/*!
    \file generator_csharp.cpp
    \brief Fast binary encoding C# generator implementation
    \author Ivan Shynkarenka
    \date 20.04.2018
    \copyright MIT License
*/

#include "generator_csharp.h"

namespace FBE {

void GeneratorCSharp::Generate(const std::shared_ptr<Package>& package)
{
    std::string domain = (package->domain && !package->domain->empty()) ? (*package->domain + ".") : "";

    GeneratePackage(domain, package);
}

void GeneratorCSharp::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(//------------------------------------------------------------------------------
// <auto-generated>
//     Automatically generated by the Fast Binary Encoding compiler, do not modify!
//     https://github.com/chronoxor/FastBinaryEncoding
//     Source: _INPUT_
//     FBE version: _VERSION_
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFooter()
{
}

void GeneratorCSharp::GenerateImports()
{
    // Generate common imports
    WriteLine();
    WriteLineIndent("using System;");
    WriteLineIndent("using System.Collections.Generic;");
    WriteLineIndent("using System.Diagnostics;");
    WriteLineIndent("using System.Globalization;");
    WriteLineIndent("using System.IO;");
    WriteLineIndent("using System.Linq;");
    WriteLineIndent("using System.Numerics;");
    WriteLineIndent("using System.Runtime.Serialization;");
    WriteLineIndent("using System.Text;");
    WriteLineIndent("using System.Threading;");
    WriteLineIndent("using System.Threading.Tasks;");
    if (JSON())
    {
        WriteLineIndent("#if UTF8JSON");
        WriteLineIndent("using Utf8Json;");
        WriteLineIndent("using Utf8Json.Resolvers;");
        WriteLineIndent("#elif NEWTONSOFTJSON");
        WriteLineIndent("using Newtonsoft.Json;");
        WriteLineIndent("#else");
        WriteLineIndent("using System.Text.Json;");
        WriteLineIndent("using System.Text.Json.Serialization;");
        WriteLineIndent("#endif");
    }
}

void GeneratorCSharp::GenerateImports(const std::string& domain, const std::shared_ptr<Package>& p)
{
    // Generate common imports
    GenerateImports();

    // Generate packages import
    if (p->import)
    {
        WriteLine();
        for (auto import : p->import->imports)
            WriteLineIndent("using " + domain + *import + ";");
    }
}

void GeneratorCSharp::GenerateFBEUuidGenerator()
{
    std::string code = R"CODE(
    // Fast Binary Encoding UUID generator
    public static class UuidGenerator
    {
        // Gregorian epoch
        private static readonly DateTime GregorianEpoch = new DateTime(1582, 10, 15, 0, 0, 0, DateTimeKind.Utc);

        // Lock and random generator
        private static readonly object Lock = new object();
        private static readonly Random Generator = new Random();

        // Node & clock sequence bytes
        private static readonly byte[] NodeBytes;
        private static readonly byte[] ClockSequenceBytes;

        // Last UUID generated timestamp
        private static DateTime _last = DateTime.UtcNow;

        static UuidGenerator()
        {
            NodeBytes = new byte[6];
            Generator.NextBytes(NodeBytes);

            ClockSequenceBytes = new byte[2];
            Generator.NextBytes(ClockSequenceBytes);
        }

        // Generate nil UUID0 (all bits set to zero)
        public static Guid Nil() { return new Guid(); }

        // Generate sequential UUID1 (time based version)
        public static Guid Sequential()
        {
            var now = DateTime.UtcNow;

            // Generate new clock sequence bytes to get rid of UUID duplicates
            lock (Lock)
            {
                if (now <= _last)
                    Generator.NextBytes(ClockSequenceBytes);
                _last = now;
            }

            long ticks = (now - GregorianEpoch).Ticks;
            Span<byte> guid = stackalloc byte[16];
            byte[] timestamp = BitConverter.GetBytes(ticks);

            // Copy node
            for (int i = 0; i < Math.Min(6, NodeBytes.Length); i++)
                guid[10 + i] = NodeBytes[i];

            // Copy clock sequence
            for (int i = 0; i < Math.Min(2, ClockSequenceBytes.Length); i++)
                guid[8 + i] = ClockSequenceBytes[i];

            // Copy timestamp
            for (int i = 0; i < Math.Min(8, timestamp.Length); i++)
                guid[i] = timestamp[i];

            // Set the variant
            guid[8] &= 0x3F;
            guid[8] |= 0x80;

            // Set the version
            guid[7] &= 0x0F;
            guid[7] |= 0x10;

            return new Guid(guid);
        }

        // Generate random UUID4 (randomly or pseudo-randomly generated version)
        public static Guid Random() { return Guid.NewGuid(); }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEBuffer()
{
    std::string code = R"CODE(
    // Fast Binary Encoding dynamic bytes buffer
    public class Buffer
    {
        private byte[] _data;
        private long _size;
        private long _offset;

        // Is the buffer empty?
        public bool IsEmpty => (_data == null) || (_size == 0);
        // Bytes memory buffer
        public byte[] Data => _data;
        // Bytes memory buffer capacity
        public long Capacity => _data.Length;
        // Bytes memory buffer size
        public long Size => _size;
        // Bytes memory buffer offset
        public long Offset => _offset;

        // Initialize a new expandable buffer with zero capacity
        public Buffer() { Attach(); }
        // Initialize a new expandable buffer with the given capacity
        public Buffer(long capacity) { Attach(capacity); }
        // Initialize a new buffer based on the specified byte array
        public Buffer(byte[] buffer) { Attach(buffer); }
        // Initialize a new buffer based on the specified region (offset) of a byte array
        public Buffer(byte[] buffer, long offset) { Attach(buffer, offset); }
        // Initialize a new buffer based on the specified region (size and offset) of a byte array
        public Buffer(byte[] buffer, long size, long offset) { Attach(buffer, size, offset); }

        #region Attach memory buffer methods

        public void Attach() { _data = new byte[0]; _size = 0; _offset = 0; }
        public void Attach(long capacity) { _data = new byte[capacity]; _size = 0; _offset = 0; }
        public void Attach(byte[] buffer) { _data = buffer; _size = buffer.Length; _offset = 0; }
        public void Attach(byte[] buffer, long offset) { _data = buffer; _size = buffer.Length; _offset = offset; }
        public void Attach(byte[] buffer, long size, long offset) { _data = buffer; _size = size; _offset = offset; }

        #endregion

        #region Memory buffer methods

        // Allocate memory in the current buffer and return offset to the allocated memory block
        public long Allocate(long size)
        {
            Debug.Assert((size >= 0), "Invalid allocation size!");
            if (size < 0)
                throw new ArgumentException("Invalid allocation size!", nameof(size));

            long offset = Size;

            // Calculate a new buffer size
            long total = _size + size;

            if (total <= Capacity)
            {
                _size = total;
                return offset;
            }

            byte[] data = new byte[Math.Max(total, 2 * Capacity)];
            Array.Copy(_data, 0, data, 0, _size);
            _data = data;
            _size = total;
            return offset;
        }

        // Remove some memory of the given size from the current buffer
        public void Remove(long offset, long size)
        {
            Debug.Assert(((offset + size) <= Size), "Invalid offset & size!");
            if ((offset + size) > Size)
                throw new ArgumentException("Invalid offset & size!", nameof(offset));

            Array.Copy(_data, offset + size, _data, offset, _size - size - offset);
            _size -= size;
            if (_offset >= (offset + size))
                _offset -= size;
            else if (_offset >= offset)
            {
                _offset -= _offset - offset;
                if (_offset > Size)
                    _offset = Size;
            }
        }

        // Reserve memory of the given capacity in the current buffer
        public void Reserve(long capacity)
        {
            Debug.Assert((capacity >= 0), "Invalid reserve capacity!");
            if (capacity < 0)
                throw new ArgumentException("Invalid reserve capacity!", nameof(capacity));

            if (capacity > Capacity)
            {
                byte[] data = new byte[Math.Max(capacity, 2 * Capacity)];
                Array.Copy(_data, 0, data, 0, _size);
                _data = data;
            }
        }

        // Resize the current buffer
        public void Resize(long size)
        {
            Reserve(size);
            _size = size;
            if (_offset > _size)
                _offset = _size;
        }

        // Reset the current buffer and its offset
        public void Reset()
        {
            _size = 0;
            _offset = 0;
        }

        // Shift the current buffer offset
        public void Shift(long offset) { _offset += offset; }
        // Unshift the current buffer offset
        public void Unshift(long offset) { _offset -= offset; }

        #endregion
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
        #region Buffer I/O methods

        public static bool ReadBool(byte[] buffer, long offset)
        {
            return buffer[offset] != 0;
        }

        public static byte ReadByte(byte[] buffer, long offset)
        {
            return buffer[offset];
        }

        public static char ReadChar(byte[] buffer, long offset)
        {
            return (char)ReadUInt8(buffer, offset);
        }

        public static char ReadWChar(byte[] buffer, long offset)
        {
            return (char)ReadUInt32(buffer, offset);
        }

        public static sbyte ReadInt8(byte[] buffer, long offset)
        {
            return (sbyte)buffer[offset];
        }

        public static byte ReadUInt8(byte[] buffer, long offset)
        {
            return buffer[offset];
        }

        public static short ReadInt16(byte[] buffer, long offset)
        {
            return (short)(buffer[offset + 0] | (buffer[offset + 1] << 8));
        }

        public static ushort ReadUInt16(byte[] buffer, long offset)
        {
            return (ushort)(buffer[offset + 0] | (buffer[offset + 1] << 8));
        }

        public static int ReadInt32(byte[] buffer, long offset)
        {
            return (buffer[offset + 0] <<  0)|
                   (buffer[offset + 1] <<  8)|
                   (buffer[offset + 2] << 16)|
                   (buffer[offset + 3] << 24);
        }

        public static uint ReadUInt32(byte[] buffer, long offset)
        {
            return ((uint)buffer[offset + 0] <<  0)|
                   ((uint)buffer[offset + 1] <<  8)|
                   ((uint)buffer[offset + 2] << 16)|
                   ((uint)buffer[offset + 3] << 24);
        }

        public static long ReadInt64(byte[] buffer, long offset)
        {
            return ((long)buffer[offset + 0] <<  0)|
                   ((long)buffer[offset + 1] <<  8)|
                   ((long)buffer[offset + 2] << 16)|
                   ((long)buffer[offset + 3] << 24)|
                   ((long)buffer[offset + 4] << 32)|
                   ((long)buffer[offset + 5] << 40)|
                   ((long)buffer[offset + 6] << 48)|
                   ((long)buffer[offset + 7] << 56);
        }

        public static ulong ReadUInt64(byte[] buffer, long offset)
        {
            return ((ulong)buffer[offset + 0] <<  0)|
                   ((ulong)buffer[offset + 1] <<  8)|
                   ((ulong)buffer[offset + 2] << 16)|
                   ((ulong)buffer[offset + 3] << 24)|
                   ((ulong)buffer[offset + 4] << 32)|
                   ((ulong)buffer[offset + 5] << 40)|
                   ((ulong)buffer[offset + 6] << 48)|
                   ((ulong)buffer[offset + 7] << 56);
        }

        public static ulong ReadUInt64Guid(byte[] buffer, long offset)
        {
            return ((ulong)buffer[offset + 0] << 24)|
                   ((ulong)buffer[offset + 1] << 16)|
                   ((ulong)buffer[offset + 2] <<  8)|
                   ((ulong)buffer[offset + 3] <<  0)|
                   ((ulong)buffer[offset + 4] << 40)|
                   ((ulong)buffer[offset + 5] << 32)|
                   ((ulong)buffer[offset + 6] << 56)|
                   ((ulong)buffer[offset + 7] << 48);
        }

        public static float ReadFloat(byte[] buffer, long offset)
        {
            var bits = default(FloatUnion);
            bits.UIntData = ReadUInt32(buffer, offset);
            return bits.FloatData;
        }

        public static double ReadDouble(byte[] buffer, long offset)
        {
            var bits = default(DoubleUnion);
            bits.ULongData = ReadUInt64(buffer, offset);
            return bits.DoubleData;
        }

        public static decimal ReadDecimal(byte[] buffer, long offset)
        {
            var bits = default(DecimalUnion);
            bits.UIntLow = ReadUInt32(buffer, offset);
            bits.UIntMid = ReadUInt32(buffer, offset + 4);
            bits.UIntHigh = ReadUInt32(buffer, offset + 8);
            bits.UIntFlags = ReadUInt32(buffer, offset + 12);
            return bits.DecimalData;
        }

        public static byte[] ReadBytes(byte[] buffer, long offset, long size)
        {
            byte[] result = new byte[size];
            Array.Copy(buffer, offset, result, 0, (int)size);
            return result;
        }

        public static string ReadString(byte[] buffer, long offset, long size)
        {
            return Encoding.UTF8.GetString(buffer, (int)offset, (int)size);
        }

        public static Guid ReadUUID(byte[] buffer, long offset)
        {
            var bits = default(GuidUnion);
            bits.ULongHigh = ReadUInt64Guid(buffer, offset);
            bits.ULongLow = ReadUInt64(buffer, offset + 8);
            return bits.GuidData;
        }

        public static void Write(byte[] buffer, long offset, bool value)
        {
            buffer[offset] = (byte)(value ? 1 : 0);
        }

        public static void Write(byte[] buffer, long offset, sbyte value)
        {
            buffer[offset] = (byte)value;
        }

        public static void Write(byte[] buffer, long offset, byte value)
        {
            buffer[offset] = value;
        }

        public static void Write(byte[] buffer, long offset, short value)
        {
            buffer[offset + 0] = (byte)(value >>  0);
            buffer[offset + 1] = (byte)(value >>  8);
        }

        public static void Write(byte[] buffer, long offset, ushort value)
        {
            buffer[offset + 0] = (byte)(value >>  0);
            buffer[offset + 1] = (byte)(value >>  8);
        }

        public static void Write(byte[] buffer, long offset, int value)
        {
            buffer[offset + 0] = (byte)(value >>  0);
            buffer[offset + 1] = (byte)(value >>  8);
            buffer[offset + 2] = (byte)(value >> 16);
            buffer[offset + 3] = (byte)(value >> 24);
        }

        public static void Write(byte[] buffer, long offset, uint value)
        {
            buffer[offset + 0] = (byte)(value >>  0);
            buffer[offset + 1] = (byte)(value >>  8);
            buffer[offset + 2] = (byte)(value >> 16);
            buffer[offset + 3] = (byte)(value >> 24);
        }

        public static void Write(byte[] buffer, long offset, long value)
        {
            buffer[offset + 0] = (byte)(value >>  0);
            buffer[offset + 1] = (byte)(value >>  8);
            buffer[offset + 2] = (byte)(value >> 16);
            buffer[offset + 3] = (byte)(value >> 24);
            buffer[offset + 4] = (byte)(value >> 32);
            buffer[offset + 5] = (byte)(value >> 40);
            buffer[offset + 6] = (byte)(value >> 48);
            buffer[offset + 7] = (byte)(value >> 56);
        }

        public static void Write(byte[] buffer, long offset, ulong value)
        {
            buffer[offset + 0] = (byte)(value >>  0);
            buffer[offset + 1] = (byte)(value >>  8);
            buffer[offset + 2] = (byte)(value >> 16);
            buffer[offset + 3] = (byte)(value >> 24);
            buffer[offset + 4] = (byte)(value >> 32);
            buffer[offset + 5] = (byte)(value >> 40);
            buffer[offset + 6] = (byte)(value >> 48);
            buffer[offset + 7] = (byte)(value >> 56);
        }

        public static void WriteGuid(byte[] buffer, long offset, ulong value)
        {
            buffer[offset + 0] = (byte)(value >> 24);
            buffer[offset + 1] = (byte)(value >> 16);
            buffer[offset + 2] = (byte)(value >>  8);
            buffer[offset + 3] = (byte)(value >>  0);
            buffer[offset + 4] = (byte)(value >> 40);
            buffer[offset + 5] = (byte)(value >> 32);
            buffer[offset + 6] = (byte)(value >> 56);
            buffer[offset + 7] = (byte)(value >> 48);
        }

        public static void Write(byte[] buffer, long offset, float value)
        {
            var bits = default(FloatUnion);
            bits.FloatData = value;
            Write(buffer, offset, bits.UIntData);
        }

        public static void Write(byte[] buffer, long offset, double value)
        {
            var bits = default(DoubleUnion);
            bits.DoubleData = value;
            Write(buffer, offset, bits.ULongData);
        }

        public static void Write(byte[] buffer, long offset, decimal value)
        {
            var bits = default(DecimalUnion);
            bits.DecimalData = value;
            Write(buffer, offset, bits.UIntLow);
            Write(buffer, offset + 4, bits.UIntMid);
            Write(buffer, offset + 8, bits.UIntHigh);
            Write(buffer, offset + 12, bits.UIntFlags);
        }

        public static void Write(byte[] buffer, long offset, byte[] value)
        {
            Array.Copy(value, 0, buffer, offset, value.Length);
        }

        public static void Write(byte[] buffer, long offset, byte[] value, long valueOffset, long valueSize)
        {
            Array.Copy(value, valueOffset, buffer, offset, valueSize);
        }

        public static void Write(byte[] buffer, long offset, byte value, long valueCount)
        {
            for (long i = 0; i < valueCount; i++)
                buffer[offset + i] = value;
        }

        public static long Write(byte[] buffer, long offset, string value)
        {
            return Encoding.UTF8.GetBytes(value, 0, value.Length, buffer, (int)offset);
        }

        public static void Write(byte[] buffer, long offset, Guid value)
        {
            var bits = default(GuidUnion);
            bits.GuidData = value;
            WriteGuid(buffer, offset, bits.ULongHigh);
            Write(buffer, offset + 8, bits.ULongLow);
        }

        #endregion

        #region Utilities

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
        private struct FloatUnion
        {
            [System.Runtime.InteropServices.FieldOffset(0)]
            public uint UIntData;
            [System.Runtime.InteropServices.FieldOffset(0)]
            public float FloatData;
        }

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
        private struct DoubleUnion
        {
            [System.Runtime.InteropServices.FieldOffset(0)]
            public ulong ULongData;
            [System.Runtime.InteropServices.FieldOffset(0)]
            public double DoubleData;
        }

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
        private struct DecimalUnion
        {
            [System.Runtime.InteropServices.FieldOffset(0)]
            public uint UIntFlags;
            [System.Runtime.InteropServices.FieldOffset(4)]
            public uint UIntHigh;
            [System.Runtime.InteropServices.FieldOffset(8)]
            public uint UIntLow;
            [System.Runtime.InteropServices.FieldOffset(12)]
            public uint UIntMid;
            [System.Runtime.InteropServices.FieldOffset(0)]
            public decimal DecimalData;
        }

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
        private struct GuidUnion
        {
            [System.Runtime.InteropServices.FieldOffset(0)]
            public ulong ULongHigh;
            [System.Runtime.InteropServices.FieldOffset(8)]
            public ulong ULongLow;
            [System.Runtime.InteropServices.FieldOffset(0)]
            public Guid GuidData;
        }

        #endregion
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEBaseModel()
{
    std::string code = R"CODE(
    // Fast Binary Encoding base model
    public class Model
    {
        // Bytes buffer
        public Buffer Buffer { get; }

        protected Model() { Buffer = new Buffer(); }
        protected Model(Buffer buffer) { Buffer = buffer; }

        #region Attach memory buffer methods

        public void Attach() { Buffer.Attach(); }
        public void Attach(long capacity) { Buffer.Attach(capacity); }
        public void Attach(byte[] buffer) { Buffer.Attach(buffer); }
        public void Attach(byte[] buffer, long offset) { Buffer.Attach(buffer, offset); }
        public void Attach(byte[] buffer, long size, long offset) { Buffer.Attach(buffer, size, offset); }
        public void Attach(Buffer buffer) { Buffer.Attach(buffer.Data, buffer.Size, buffer.Offset); }
        public void Attach(Buffer buffer, long offset) { Buffer.Attach(buffer.Data, buffer.Size, offset); }

        #endregion

        #region Memory buffer methods

        public long Allocate(long size) { return Buffer.Allocate(size); }
        public void Remove(long offset, long size) { Buffer.Remove(offset, size); }
        public void Reserve(long capacity) { Buffer.Reserve(capacity); }
        public void Resize(long size) { Buffer.Resize(size); }
        public void Reset() { Buffer.Reset(); }
        public void Shift(long offset) { Buffer.Shift(offset); }
        public void Unshift(long offset) { Buffer.Unshift(offset); }

        #endregion

        #region Buffer I/O methods

        protected uint ReadUInt32(long offset) { return Buffer.ReadUInt32(Buffer.Data, Buffer.Offset + offset); }
        protected void Write(long offset, uint value) { Buffer.Write(Buffer.Data, Buffer.Offset + offset, value); }

        #endregion
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelBase()
{
    std::string code = R"CODE(
    // Fast Binary Encoding base types enumeration
    public enum BaseTypes
    {
        BOOL,
        BYTE,
        BYTES,
        CHAR,
        WCHAR,
        INT8,
        UINT8,
        INT16,
        UINT16,
        INT32,
        UINT32,
        INT64,
        UINT64,
        FLOAT,
        DOUBLE,
        DECIMAL,
        UUID,
        STRING,
        TIMESTAMP
    }

    // Fast Binary Encoding base field model
    public abstract class FieldModelBase
    {
        protected Buffer _buffer;
        protected long _offset;

        protected FieldModelBase(Buffer buffer, long offset)
        {
            _buffer = buffer;
            _offset = offset;
        }

        // Get the field offset
        public long FBEOffset { get => _offset; set => _offset = value; }
        // Get the field size
        public virtual long FBESize => 0;
        // Get the field extra size
        public virtual long FBEExtra => 0;

        // Shift the current field offset
        public void FBEShift(long size) { _offset += size; }
        // Unshift the current field offset
        public void FBEUnshift(long size) { _offset -= size; }

        #region Buffer I/O methods

        protected bool ReadBool(long offset) { return Buffer.ReadBool(_buffer.Data, _buffer.Offset + offset); }
        protected byte ReadByte(long offset) { return Buffer.ReadByte(_buffer.Data, _buffer.Offset + offset); }
        protected char ReadChar(long offset) { return Buffer.ReadChar(_buffer.Data, _buffer.Offset + offset); }
        protected char ReadWChar(long offset) { return Buffer.ReadWChar(_buffer.Data, _buffer.Offset + offset); }
        protected sbyte ReadInt8(long offset) { return Buffer.ReadInt8(_buffer.Data, _buffer.Offset + offset); }
        protected byte ReadUInt8(long offset) { return Buffer.ReadUInt8(_buffer.Data, _buffer.Offset + offset); }
        protected short ReadInt16(long offset) { return Buffer.ReadInt16(_buffer.Data, _buffer.Offset + offset); }
        protected ushort ReadUInt16(long offset) { return Buffer.ReadUInt16(_buffer.Data, _buffer.Offset + offset); }
        protected int ReadInt32(long offset) { return Buffer.ReadInt32(_buffer.Data, _buffer.Offset + offset); }
        protected uint ReadUInt32(long offset) { return Buffer.ReadUInt32(_buffer.Data, _buffer.Offset + offset); }
        protected long ReadInt64(long offset) { return Buffer.ReadInt64(_buffer.Data, _buffer.Offset + offset); }
        protected ulong ReadUInt64(long offset) { return Buffer.ReadUInt64(_buffer.Data, _buffer.Offset + offset); }
        protected float ReadFloat(long offset) { return Buffer.ReadFloat(_buffer.Data, _buffer.Offset + offset); }
        protected double ReadDouble(long offset) { return Buffer.ReadDouble(_buffer.Data, _buffer.Offset + offset); }
        protected decimal ReadDecimal(long offset) { return Buffer.ReadDecimal(_buffer.Data, _buffer.Offset + offset); }
        protected byte[] ReadBytes(long offset, long size) { return Buffer.ReadBytes(_buffer.Data, _buffer.Offset + offset, size); }
        protected string ReadString(long offset, long size) { return Buffer.ReadString(_buffer.Data, _buffer.Offset + offset, size); }
        protected Guid ReadUUID(long offset) { return Buffer.ReadUUID(_buffer.Data, _buffer.Offset + offset); }
        protected void Write(long offset, bool value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, sbyte value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, byte value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, short value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, ushort value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, int value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, uint value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, long value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, ulong value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, float value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, double value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, decimal value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, byte[] value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, byte[] value, long valueOffset, long valueSize) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value, valueOffset, valueSize); }
        protected void Write(long offset, byte value, long valueCount) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value, valueCount); }
        protected long Write(long offset, string value) { return Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }
        protected void Write(long offset, Guid value) { Buffer.Write(_buffer.Data, _buffer.Offset + offset, value); }

        #endregion
    }

    // Fast Binary Encoding field model value type
    public abstract class FieldModelValueType<T> : FieldModelBase
        where T : struct
    {
        protected FieldModelValueType(Buffer buffer, long offset) : base(buffer, offset) {}

        // Clone the field model
        public abstract FieldModelValueType<T> Clone();

        // Check if the value is valid
        public virtual bool Verify() { return true; }

        // Get the value
        public abstract void Get(out T value);
        public abstract void Get(out T value, T defaults);

        // Set the value
        public abstract void Set(T value);

        // Create field model of the given type
        public static FieldModelValueType<T> CreateFieldModel(BaseTypes type, Buffer buffer, long offset)
        {
            switch (type)
            {
                case BaseTypes.BOOL:
                    return new FieldModelBool(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.BYTE:
                    return new FieldModelByte(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.CHAR:
                    return new FieldModelChar(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.WCHAR:
                    return new FieldModelWChar(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.INT8:
                    return new FieldModelInt8(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.UINT8:
                    return new FieldModelUInt8(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.INT16:
                    return new FieldModelInt16(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.UINT16:
                    return new FieldModelUInt16(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.INT32:
                    return new FieldModelInt32(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.UINT32:
                    return new FieldModelUInt32(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.INT64:
                    return new FieldModelInt64(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.UINT64:
                    return new FieldModelUInt64(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.FLOAT:
                    return new FieldModelFloat(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.DOUBLE:
                    return new FieldModelDouble(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.DECIMAL:
                    return new FieldModelDecimal(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.UUID:
                    return new FieldModelUUID(buffer, offset) as FieldModelValueType<T>;
                case BaseTypes.TIMESTAMP:
                    return new FieldModelTimestamp(buffer, offset) as FieldModelValueType<T>;
                default:
                    Debug.Assert(false, "Unknown type!");
                    return null;
            }
        }
    }

    // Fast Binary Encoding field model reference type
    public abstract class FieldModelReferenceType<T> : FieldModelBase
        where T : class
    {
        protected FieldModelReferenceType(Buffer buffer, long offset) : base(buffer, offset) {}

        // Clone the field model
        public abstract FieldModelReferenceType<T> Clone();

        // Check if the value is valid
        public virtual bool Verify() { return true; }

        // Get the value
        public abstract void Get(out T value);
        public abstract void Get(out T value, T defaults);

        // Set the value
        public abstract void Set(T value);

        // Create field model of the given type
        public static FieldModelReferenceType<T> CreateFieldModel(BaseTypes type, Buffer buffer, long offset)
        {
            switch (type)
            {
                case BaseTypes.BYTES:
                    return new FieldModelBytes(buffer, offset) as FieldModelReferenceType<T>;
                case BaseTypes.STRING:
                    return new FieldModelString(buffer, offset) as FieldModelReferenceType<T>;
                default:
                    Debug.Assert(false, "Unknown type!");
                    return null;
            }
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModel(const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(
    // Fast Binary Encoding _TYPE_ field model
    public class FieldModel_NAME_ : FieldModelValueType<_TYPE_>
    {
        public FieldModel_NAME_(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the field size
        public override long FBESize => _SIZE_;

        // Clone the field model
        public override FieldModelValueType<_TYPE_> Clone() { return new FieldModel_NAME_(_buffer, _offset); }

        // Get the value
        public override void Get(out _TYPE_ value) { Get(out value, _DEFAULTS_); }
        public override void Get(out _TYPE_ value, _TYPE_ defaults)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = defaults;
                return;
            }

            value = Read_NAME_(FBEOffset);
        }

        // Set the value
        public override void Set(_TYPE_ value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            Write(FBEOffset, _BASE_value);
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelTimestamp()
{
    std::string code = R"CODE(
    // Fast Binary Encoding timestamp field model
    public class FieldModelTimestamp : FieldModelValueType<DateTime>
    {
        private const long UnixEpoch = 621355968000000000;

        public FieldModelTimestamp(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the field size
        public override long FBESize => 8;

        // Clone the field model
        public override FieldModelValueType<DateTime> Clone() { return new FieldModelTimestamp(_buffer, _offset); }

        // Get the timestamp value
        public override void Get(out DateTime value) { Get(out value, new DateTime(UnixEpoch, DateTimeKind.Utc)); }
        public override void Get(out DateTime value, DateTime defaults)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = defaults;
                return;
            }

            ulong ticks = ReadUInt64(FBEOffset) / 100;
            value = new DateTime((long)(UnixEpoch + ticks), DateTimeKind.Utc);
        }

        // Set the timestamp value
        public override void Set(DateTime value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            ulong nanoseconds = (ulong)((value.Ticks - UnixEpoch) * 100);
            Write(FBEOffset, nanoseconds);
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelBytes()
{
    std::string code = R"CODE(
    // Fast Binary Encoding bytes field model
    public class FieldModelBytes : FieldModelReferenceType<MemoryStream>
    {
        public FieldModelBytes(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the field size
        public override long FBESize => 4;
        // Get the field extra size
        public override long FBEExtra
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeBytesOffset = ReadUInt32(FBEOffset);
                if ((fbeBytesOffset == 0) || ((_buffer.Offset + fbeBytesOffset + 4) > _buffer.Size))
                    return 0;

                uint fbeBytesSize = ReadUInt32(fbeBytesOffset);
                return 4 + fbeBytesSize;
            }
        }

        // Clone the field model
        public override FieldModelReferenceType<MemoryStream> Clone() { return new FieldModelBytes(_buffer, _offset); }

        // Check if the bytes value is valid
        public override bool Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return true;

            uint fbeBytesOffset = ReadUInt32(FBEOffset);
            if (fbeBytesOffset == 0)
                return true;

            if ((_buffer.Offset + fbeBytesOffset + 4) > _buffer.Size)
                return false;

            uint fbeBytesSize = ReadUInt32(fbeBytesOffset);
            if ((_buffer.Offset + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.Size)
                return false;

            return true;
        }

        // Get the bytes value
        public override void Get(out MemoryStream value) { Get(out value, new MemoryStream()); }
        public override void Get(out MemoryStream value, MemoryStream defaults)
        {
            Debug.Assert((defaults != null), "Invalid default bytes value!");
            if (defaults == null)
                throw new ArgumentNullException(nameof(defaults), "Invalid default bytes value!");

            value = defaults;

            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            uint fbeBytesOffset = ReadUInt32(FBEOffset);
            if (fbeBytesOffset == 0)
                return;

            Debug.Assert(((_buffer.Offset + fbeBytesOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + fbeBytesOffset + 4) > _buffer.Size)
                return;

            uint fbeBytesSize = ReadUInt32(fbeBytesOffset);
            Debug.Assert(((_buffer.Offset + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.Size)
                return;

            var buffer = ReadBytes(fbeBytesOffset + 4, fbeBytesSize);
            value.Write(buffer, 0, buffer.Length);
        }

        // Set the bytes value
        public override void Set(MemoryStream value)
        {
            Debug.Assert((value != null), "Invalid bytes value!");
            if (value == null)
                throw new ArgumentNullException(nameof(value), "Invalid bytes value!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            uint fbeBytesSize = (uint)value.Length;
            uint fbeBytesOffset = (uint)(_buffer.Allocate(4 + fbeBytesSize) - _buffer.Offset);
            Debug.Assert(((fbeBytesOffset > 0) && ((_buffer.Offset + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.Size)), "Model is broken!");
            if ((fbeBytesOffset == 0) || ((_buffer.Offset + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.Size))
                return;

            Write(FBEOffset, fbeBytesOffset);
            Write(fbeBytesOffset, fbeBytesSize);
            Write(fbeBytesOffset + 4, value.GetBuffer(), 0, fbeBytesSize);
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelString()
{
    std::string code = R"CODE(
    // Fast Binary Encoding string field model
    public class FieldModelString : FieldModelReferenceType<string>
    {
        public FieldModelString(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the field size
        public override long FBESize => 4;
        // Get the field extra size
        public override long FBEExtra
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeStringOffset = ReadUInt32(FBEOffset);
                if ((fbeStringOffset == 0) || ((_buffer.Offset + fbeStringOffset + 4) > _buffer.Size))
                    return 0;

                uint fbeStringSize = ReadUInt32(fbeStringOffset);
                return 4 + fbeStringSize;
            }
        }

        // Clone the field model
        public override FieldModelReferenceType<string> Clone() { return new FieldModelString(_buffer, _offset); }

        // Check if the string value is valid
        public override bool Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return true;

            uint fbeStringOffset = ReadUInt32(FBEOffset);
            if (fbeStringOffset == 0)
                return true;

            if ((_buffer.Offset + fbeStringOffset + 4) > _buffer.Size)
                return false;

            uint fbeStringSize = ReadUInt32(fbeStringOffset);
            if ((_buffer.Offset + fbeStringOffset + 4 + fbeStringSize) > _buffer.Size)
                return false;

            return true;
        }

        // Get the string value
        public override void Get(out string value) { Get(out value, ""); }
        public override void Get(out string value, string defaults)
        {
            Debug.Assert((defaults != null), "Invalid default string value!");
            if (defaults == null)
                throw new ArgumentNullException(nameof(defaults), "Invalid default string value!");

            value = defaults;

            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            uint fbeStringOffset = ReadUInt32(FBEOffset);
            if (fbeStringOffset == 0)
                return;

            Debug.Assert(((_buffer.Offset + fbeStringOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + fbeStringOffset + 4) > _buffer.Size)
                return;

            uint fbeStringSize = ReadUInt32(fbeStringOffset);
            Debug.Assert(((_buffer.Offset + fbeStringOffset + 4 + fbeStringSize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + fbeStringOffset + 4 + fbeStringSize) > _buffer.Size)
                return;

            value = ReadString(fbeStringOffset + 4, fbeStringSize);
        }

        // Set the string value
        public override void Set(string value)
        {
            Debug.Assert((value != null), "Invalid string value!");
            if (value == null)
                throw new ArgumentNullException(nameof(value), "Invalid string value!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            uint fbeStringSize = (uint)Encoding.UTF8.GetByteCount(value);
            uint fbeStringOffset = (uint)(_buffer.Allocate(4 + fbeStringSize) - _buffer.Offset);
            Debug.Assert(((fbeStringOffset > 0) && ((_buffer.Offset + fbeStringOffset + 4 + fbeStringSize) <= _buffer.Size)), "Model is broken!");
            if ((fbeStringOffset == 0) || ((_buffer.Offset + fbeStringOffset + 4 + fbeStringSize) > _buffer.Size))
                return;

            Write(FBEOffset, fbeStringOffset);
            Write(fbeStringOffset, fbeStringSize);
            Write(fbeStringOffset + 4, value);
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelOptional(bool valueType)
{
    std::string code = R"CODE(
    // Fast Binary Encoding field model optional (_NAME_)
    public class FieldModelOptional_TYPE_<T, TModel> : FieldModelBase
        where T : _BASE_
        where TModel : FieldModel_TYPE_<T>
    {
        public FieldModelOptional_TYPE_(TModel model, Buffer buffer, long offset) : base(buffer, offset)
        {
            Value = model.Clone() as TModel;
            // Reset the optional model offset
            if (Value != null)
                Value.FBEOffset = 0;
        }

        // Get the field size
        public override long FBESize => 1 + 4;
        // Get the field extra size
        public override long FBEExtra
        {
            get
            {
                if (!HasValue)
                    return 0;

                uint fbeOptionalOffset = ReadUInt32(FBEOffset + 1);
                if ((fbeOptionalOffset == 0) || ((_buffer.Offset + fbeOptionalOffset + 4) > _buffer.Size))
                    return 0;

                _buffer.Shift(fbeOptionalOffset);
                long fbeResult = Value.FBESize + Value.FBEExtra;
                _buffer.Unshift(fbeOptionalOffset);
                return fbeResult;
            }
        }

        // Clone the field model
        public FieldModelOptional_TYPE_<T, TModel> Clone() { return new FieldModelOptional_TYPE_<T, TModel>(Value, _buffer, _offset); }

        //! Is the value present?
        public static implicit operator bool(FieldModelOptional_TYPE_<T, TModel> optional) { return !ReferenceEquals(optional, null) && optional.HasValue; }

        // Checks if the object contains a value
        public bool HasValue
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return false;

                byte fbeHasValue = ReadUInt8(FBEOffset);
                return (fbeHasValue != 0);
            }
        }

        // Base field model value
        public TModel Value { get; }

        // Check if the optional value is valid
        public virtual bool Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return true;

            byte fbeHasValue = ReadUInt8(FBEOffset);
            if (fbeHasValue == 0)
                return true;

            uint fbeOptionalOffset = ReadUInt32(FBEOffset + 1);
            if (fbeOptionalOffset == 0)
                return false;

            _buffer.Shift(fbeOptionalOffset);
            bool fbeResult = Value.Verify();
            _buffer.Unshift(fbeOptionalOffset);
            return fbeResult;
        }

        // Get the optional value (being phase)
        public long GetBegin()
        {
            if (!HasValue)
                return 0;

            uint fbeOptionalOffset = ReadUInt32(FBEOffset + 1);
            Debug.Assert((fbeOptionalOffset > 0), "Model is broken!");
            if (fbeOptionalOffset == 0)
                return 0;

            _buffer.Shift(fbeOptionalOffset);
            return fbeOptionalOffset;
        }

        // Get the optional value (end phase)
        public void GetEnd(long fbeBegin)
        {
            _buffer.Unshift(fbeBegin);
        }

        // Get the optional value
        public void Get(out _ARGS_ optional, _ARGS_ defaults = null)
        {
            optional = defaults;

            long fbeBegin = GetBegin();
            if (fbeBegin == 0)
                return;

            Value.Get(out var temp);
            optional = temp;

            GetEnd(fbeBegin);
        }

        // Set the optional value (begin phase)
        public long SetBegin(bool hasValue)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return 0;

            byte fbeHasValue = (byte)(hasValue ? 1 : 0);
            Write(FBEOffset, fbeHasValue);
            if (fbeHasValue == 0)
                return 0;

            uint fbeOptionalSize = (uint)Value.FBESize;
            uint fbeOptionalOffset = (uint)(_buffer.Allocate(fbeOptionalSize) - _buffer.Offset);
            Debug.Assert(((fbeOptionalOffset > 0) && ((_buffer.Offset + fbeOptionalOffset + fbeOptionalSize) <= _buffer.Size)), "Model is broken!");
            if ((fbeOptionalOffset == 0) || ((_buffer.Offset + fbeOptionalOffset + fbeOptionalSize) > _buffer.Size))
                return 0;

            Write(FBEOffset + 1, fbeOptionalOffset);

            _buffer.Shift(fbeOptionalOffset);
            return fbeOptionalOffset;
        }

        // Set the optional value (end phase)
        public void SetEnd(long fbeBegin)
        {
            _buffer.Unshift(fbeBegin);
        }

        // Set the optional value
        public void Set(_ARGS_ optional)
        {
            long fbeBegin = SetBegin(optional_HAS_VALUE_);
            if (fbeBegin == 0)
                return;

            if (optional_HAS_VALUE_)
                Value.Set(optional_VALUE_);

            SetEnd(fbeBegin);
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), (valueType ? "value type" : "reference type"));
    code = std::regex_replace(code, std::regex("_TYPE_"), (valueType ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_BASE_"), (valueType ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_"), (valueType ? "T?" : "T"));
    code = std::regex_replace(code, std::regex("_HAS_VALUE_"), (valueType ? ".HasValue" : " != null"));
    code = std::regex_replace(code, std::regex("_VALUE_"), (valueType ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelArray(bool valueType, bool optional)
{
    std::string code = R"CODE(
    // Fast Binary Encoding field model array (_NAME_)
    public class FieldModelArray_TYPE_<T, TModel> : FieldModelBase
        where T : _BASE_
        where TModel : FieldModel_MODEL_REF_<T>
    {
        private readonly _MODEL_DECL_ _model;
        private readonly long _size;

        public FieldModelArray_TYPE_(TModel model, Buffer buffer, long offset, long size) : base(buffer, offset)
        {
            _model = _MODEL_CLONE_;
            _size = size;
        }

        // Get the field size
        public override long FBESize => _size * _model.FBESize;
        // Get the field extra size
        public override long FBEExtra => 0;

        // Clone the field model
        public FieldModelArray_TYPE_<T, TModel> Clone() { return new FieldModelArray_TYPE_<T, TModel>(_model_MODEL_VALUE_, _buffer, _offset, _size); }

        // Get the array offset
        public long Offset => 0;
        // Get the array size
        public long Size => _size;

        // Array index operator
        public _MODEL_DECL_ this[long index]
        {
            get
            {
                Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
                Debug.Assert((index < _size), "Index is out of bounds!");

                _model.FBEOffset = FBEOffset;
                _model.FBEShift(index * _model.FBESize);
                return _model;
            }
        }

        // Check if the array is valid
        public virtual bool Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return false;

            _model.FBEOffset = FBEOffset;
            for (long i = _size; i-- > 0;)
            {
                if (!_model.Verify())
                    return false;
                _model.FBEShift(_model.FBESize);
            }

            return true;
        }

        // Get the array
        public void Get(ref _ARGS_[] values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values = new _ARGS_[_size];

            var fbeModel = this[0];
            for (long i = 0; i < _size; i++)
            {
                fbeModel.Get(out values[i]);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Get the array as List
        public void Get(ref List<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values.Clear();
            values.Capacity = (int)_size;

            var fbeModel = this[0];
            for (long i = _size; i-- > 0;)
            {
                fbeModel.Get(out var value);
                values.Add(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Set the array
        public void Set(_ARGS_[] values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = this[0];
            for (long i = 0; (i < values.Length) && (i < _size); i++)
            {
                fbeModel.Set(values[i]);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Set the array as List
        public void Set(List<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = this[0];
            for (long i = 0; (i < values.Count) && (i < _size); i++)
            {
                fbeModel.Set(values[(int)i]);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), (optional ? (valueType ? "optional value type" : "optional reference type") : (valueType ? "value type" : "reference type")));
    code = std::regex_replace(code, std::regex("_TYPE_"), (optional ? (valueType ? "OptionalValueType" : "OptionalReferenceType") : (valueType ? "ValueType" : "ReferenceType")));
    code = std::regex_replace(code, std::regex("_BASE_"), (valueType ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_"), ((optional && valueType) ? "T?" : "T"));
    code = std::regex_replace(code, std::regex("_MODEL_CLONE_"), (optional ? "new FieldModelOptional_MODEL_REF_<T, TModel>(model, buffer, offset)" : "model.Clone() as TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_DECL_"), (optional ? "FieldModelOptional_MODEL_REF_<T, TModel>" : "TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_REF_"), (valueType ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_"), (optional ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelVector(bool valueType, bool optional)
{
    std::string code = R"CODE(
    // Fast Binary Encoding field model vector (_NAME_)
    public class FieldModelVector_TYPE_<T, TModel> : FieldModelBase
        where T : _BASE_
        where TModel : FieldModel_MODEL_REF_<T>
    {
        private readonly _MODEL_DECL_ _model;

        public FieldModelVector_TYPE_(TModel model, Buffer buffer, long offset) : base(buffer, offset)
        {
            _model = _MODEL_CLONE_;
        }

        // Get the field size
        public override long FBESize => 4;
        // Get the field extra size
        public override long FBEExtra
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeVectorOffset = ReadUInt32(FBEOffset);
                if ((fbeVectorOffset == 0) || ((_buffer.Offset + fbeVectorOffset + 4) > _buffer.Size))
                    return 0;

                uint fbeVectorSize = ReadUInt32(fbeVectorOffset);

                long fbeResult = 4;
                _model.FBEOffset = fbeVectorOffset + 4;
                for (uint i = fbeVectorSize; i-- > 0;)
                {
                    fbeResult += _model.FBESize + _model.FBEExtra;
                    _model.FBEShift(_model.FBESize);
                }
                return fbeResult;
            }
        }

        // Clone the field model
        public FieldModelVector_TYPE_<T, TModel> Clone() { return new FieldModelVector_TYPE_<T, TModel>(_model_MODEL_VALUE_, _buffer, _offset); }

        // Get the vector offset
        public long Offset
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeVectorOffset = ReadUInt32(FBEOffset);
                return fbeVectorOffset;
            }
        }

        // Get the vector size
        public long Size
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeVectorOffset = ReadUInt32(FBEOffset);
                if ((fbeVectorOffset == 0) || ((_buffer.Offset + fbeVectorOffset + 4) > _buffer.Size))
                    return 0;

                uint fbeVectorSize = ReadUInt32(fbeVectorOffset);
                return fbeVectorSize;
            }
        }

        // Vector index operator
        public _MODEL_DECL_ this[long index]
        {
            get
            {
                Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");

                uint fbeVectorOffset = ReadUInt32(FBEOffset);
                Debug.Assert(((fbeVectorOffset > 0) && ((_buffer.Offset + fbeVectorOffset + 4) <= _buffer.Size)), "Model is broken!");

                uint fbeVectorSize = ReadUInt32(fbeVectorOffset);
                Debug.Assert((index < fbeVectorSize), "Index is out of bounds!");

                _model.FBEOffset = fbeVectorOffset + 4;
                _model.FBEShift(index * _model.FBESize);
                return _model;
            }
        }

        // Resize the vector and get its first model
        public _MODEL_DECL_ Resize(long size)
        {
            uint fbeVectorSize = (uint)(size * _model.FBESize);
            uint fbeVectorOffset = (uint)(_buffer.Allocate(4 + fbeVectorSize) - _buffer.Offset);
            Debug.Assert(((fbeVectorOffset > 0) && ((_buffer.Offset + fbeVectorOffset + 4) <= _buffer.Size)), "Model is broken!");

            Write(FBEOffset, fbeVectorOffset);
            Write(fbeVectorOffset, (uint)size);
            Write(fbeVectorOffset + 4, 0, fbeVectorSize);

            _model.FBEOffset = fbeVectorOffset + 4;
            return _model;
        }

        // Check if the vector is valid
        public virtual bool Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return true;

            uint fbeVectorOffset = ReadUInt32(FBEOffset);
            if (fbeVectorOffset == 0)
                return true;

            if ((_buffer.Offset + fbeVectorOffset + 4) > _buffer.Size)
                return false;

            uint fbeVectorSize = ReadUInt32(fbeVectorOffset);

            _model.FBEOffset = fbeVectorOffset + 4;
            for (uint i = fbeVectorSize; i-- > 0;)
            {
                if (!_model.Verify())
                    return false;
                _model.FBEShift(_model.FBESize);
            }

            return true;
        }

        // Get the vector as List
        public void Get(ref List<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values.Clear();

            long fbeVectorSize = Size;
            if (fbeVectorSize == 0)
                return;

            values.Capacity = (int)fbeVectorSize;

            var fbeModel = this[0];
            for (long i = fbeVectorSize; i-- > 0;)
            {
                fbeModel.Get(out var value);
                values.Add(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Get the vector as LinkedList
        public void Get(ref LinkedList<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values.Clear();

            long fbeVectorSize = Size;
            if (fbeVectorSize == 0)
                return;

            var fbeModel = this[0];
            for (long i = fbeVectorSize; i-- > 0;)
            {
                fbeModel.Get(out var value);
                values.AddLast(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Get the vector as HashSet
        public void Get(ref HashSet<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values.Clear();

            long fbeVectorSize = Size;
            if (fbeVectorSize == 0)
                return;

            var fbeModel = this[0];
            for (long i = fbeVectorSize; i-- > 0;)
            {
                fbeModel.Get(out var value);
                values.Add(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Set the vector as List
        public void Set(List<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = Resize(values.Count);
            foreach (var value in values)
            {
                fbeModel.Set(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Set the vector as LinkedList
        public void Set(LinkedList<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = Resize(values.Count);
            foreach (var value in values)
            {
                fbeModel.Set(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }

        // Set the vector as HashSet
        public void Set(HashSet<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = Resize(values.Count);
            foreach (var value in values)
            {
                fbeModel.Set(value);
                fbeModel.FBEShift(fbeModel.FBESize);
            }
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), (optional ? (valueType ? "optional value type" : "optional reference type") : (valueType ? "value type" : "reference type")));
    code = std::regex_replace(code, std::regex("_TYPE_"), (optional ? (valueType ? "OptionalValueType" : "OptionalReferenceType") : (valueType ? "ValueType" : "ReferenceType")));
    code = std::regex_replace(code, std::regex("_BASE_"), (valueType ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_"), ((optional && valueType) ? "T?" : "T"));
    code = std::regex_replace(code, std::regex("_MODEL_CLONE_"), (optional ? "new FieldModelOptional_MODEL_REF_<T, TModel>(model, buffer, offset)" : "model.Clone() as TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_DECL_"), (optional ? "FieldModelOptional_MODEL_REF_<T, TModel>" : "TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_REF_"), (valueType ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_"), (optional ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFieldModelMap(bool valueTypeKey, bool valueTypeValue, bool optional)
{
    std::string code = R"CODE(
    // Fast Binary Encoding field model map (_NAME_KEY_, _NAME_VALUE_)
    public class FieldModelMap_TYPE_KEY__TYPE_VALUE_<TKey, TKeyModel, TValue, TValueModel> : FieldModelBase
        where TKey : _BASE_KEY_
        where TKeyModel : FieldModel_MODEL_KEY_REF_<TKey>
        where TValue : _BASE_VALUE_
        where TValueModel : FieldModel_MODEL_VALUE_REF_<TValue>
    {
        private readonly _MODEL_KEY_DECL_ _modelKey;
        private readonly _MODEL_VALUE_DECL_ _modelValue;

        public FieldModelMap_TYPE_KEY__TYPE_VALUE_(TKeyModel modelKey, TValueModel modelValue, Buffer buffer, long offset) : base(buffer, offset)
        {
            _modelKey = _MODEL_KEY_CLONE_;
            _modelValue = _MODEL_VALUE_CLONE_;
        }

        // Get the field size
        public override long FBESize => 4;
        // Get the field extra size
        public override long FBEExtra
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeMapOffset = ReadUInt32(FBEOffset);
                if ((fbeMapOffset == 0) || ((_buffer.Offset + fbeMapOffset + 4) > _buffer.Size))
                    return 0;

                uint fbeMapSize = ReadUInt32(fbeMapOffset);

                long fbeResult = 4;
                _modelKey.FBEOffset = fbeMapOffset + 4;
                _modelValue.FBEOffset = fbeMapOffset + 4 + _modelKey.FBESize;
                for (uint i = fbeMapSize; i-- > 0;)
                {
                    fbeResult += _modelKey.FBESize + _modelKey.FBEExtra;
                    _modelKey.FBEShift(_modelKey.FBESize + _modelValue.FBESize);

                    fbeResult += _modelValue.FBESize + _modelValue.FBEExtra;
                    _modelValue.FBEShift(_modelKey.FBESize + _modelValue.FBESize);
                }
                return fbeResult;
            }
        }

        // Clone the field model
        public FieldModelMap_TYPE_KEY__TYPE_VALUE_<TKey, TKeyModel, TValue, TValueModel> Clone() { return new FieldModelMap_TYPE_KEY__TYPE_VALUE_<TKey, TKeyModel, TValue, TValueModel>(_modelKey_MODEL_KEY_VALUE_, _modelValue_MODEL_VALUE_VALUE_, _buffer, _offset); }

        // Get the map offset
        public long Offset
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeMapOffset = ReadUInt32(FBEOffset);
                return fbeMapOffset;
            }
        }

        // Get the map size
        public long Size
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                    return 0;

                uint fbeMapOffset = ReadUInt32(FBEOffset);
                if ((fbeMapOffset == 0) || ((_buffer.Offset + fbeMapOffset + 4) > _buffer.Size))
                    return 0;

                uint fbeMapSize = ReadUInt32(fbeMapOffset);
                return fbeMapSize;
            }
        }

        // Map index operator
        public KeyValuePair<_MODEL_KEY_DECL_, _MODEL_VALUE_DECL_> this[long index]
        {
            get
            {
                Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");

                uint fbeMapOffset = ReadUInt32(FBEOffset);
                Debug.Assert(((fbeMapOffset > 0) && ((_buffer.Offset + fbeMapOffset + 4) <= _buffer.Size)), "Model is broken!");

                uint fbeMapSize = ReadUInt32(fbeMapOffset);
                Debug.Assert((index < fbeMapSize), "Index is out of bounds!");

                _modelKey.FBEOffset = fbeMapOffset + 4;
                _modelValue.FBEOffset = fbeMapOffset + 4 + _modelKey.FBESize;
                _modelKey.FBEShift(index * (_modelKey.FBESize + _modelValue.FBESize));
                _modelValue.FBEShift(index * (_modelKey.FBESize + _modelValue.FBESize));
                return new KeyValuePair<_MODEL_KEY_DECL_, _MODEL_VALUE_DECL_>(_modelKey, _modelValue);
            }
        }

        // Resize the map and get its first model
        public KeyValuePair<_MODEL_KEY_DECL_, _MODEL_VALUE_DECL_> Resize(long size)
        {
            uint fbeMapSize = (uint)(size * (_modelKey.FBESize + _modelValue.FBESize));
            uint fbeMapOffset = (uint)(_buffer.Allocate(4 + fbeMapSize) - _buffer.Offset);
            Debug.Assert(((fbeMapOffset > 0) && ((_buffer.Offset + fbeMapOffset + 4) <= _buffer.Size)), "Model is broken!");

            Write(FBEOffset, fbeMapOffset);
            Write(fbeMapOffset, (uint)size);
            Write(fbeMapOffset + 4, 0, fbeMapSize);

            _modelKey.FBEOffset = fbeMapOffset + 4;
            _modelValue.FBEOffset = fbeMapOffset + 4 + _modelKey.FBESize;
            return new KeyValuePair<_MODEL_KEY_DECL_, _MODEL_VALUE_DECL_>(_modelKey, _modelValue);
        }

        // Check if the map is valid
        public virtual bool Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return true;

            uint fbeMapOffset = ReadUInt32(FBEOffset);
            if (fbeMapOffset == 0)
                return true;

            if ((_buffer.Offset + fbeMapOffset + 4) > _buffer.Size)
                return false;

            uint fbeMapSize = ReadUInt32(fbeMapOffset);

            _modelKey.FBEOffset = fbeMapOffset + 4;
            _modelValue.FBEOffset = fbeMapOffset + 4 + _modelKey.FBESize;
            for (uint i = fbeMapSize; i-- > 0;)
            {
                if (!_modelKey.Verify())
                    return false;
                _modelKey.FBEShift(_modelKey.FBESize + _modelValue.FBESize);
                if (!_modelValue.Verify())
                    return false;
                _modelValue.FBEShift(_modelKey.FBESize + _modelValue.FBESize);
            }

            return true;
        }

        // Get the map as Dictionary
        public void Get(ref Dictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values.Clear();

            long fbeMapSize = Size;
            if (fbeMapSize == 0)
                return;

            var fbeModel = this[0];
            for (long i = fbeMapSize; i-- > 0;)
            {
                fbeModel.Key.Get(out var key);
                fbeModel.Value.Get(out var value);
                values.Add(key, value);
                fbeModel.Key.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
                fbeModel.Value.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
            }
        }

        // Get the map as SortedDictionary
        public void Get(ref SortedDictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            values.Clear();

            long fbeMapSize = Size;
            if (fbeMapSize == 0)
                return;

            var fbeModel = this[0];
            for (long i = fbeMapSize; i-- > 0;)
            {
                fbeModel.Key.Get(out var key);
                fbeModel.Value.Get(out var value);
                values.Add(key, value);
                fbeModel.Key.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
                fbeModel.Value.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
            }
        }

        // Set the map as Dictionary
        public void Set(Dictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = Resize(values.Count);
            foreach (var value in values)
            {
                fbeModel.Key.Set(value.Key);
                fbeModel.Key.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
                fbeModel.Value.Set(value.Value);
                fbeModel.Value.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
            }
        }

        // Set the map as SortedDictionary
        public void Set(SortedDictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            var fbeModel = Resize(values.Count);
            foreach (var value in values)
            {
                fbeModel.Key.Set(value.Key);
                fbeModel.Key.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
                fbeModel.Value.Set(value.Value);
                fbeModel.Value.FBEShift(fbeModel.Key.FBESize + fbeModel.Value.FBESize);
            }
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_KEY_"), (valueTypeKey ? "value type key" : "reference type key"));
    code = std::regex_replace(code, std::regex("_NAME_VALUE_"), (optional ? (valueTypeValue ? "optional value type value" : "optional reference type value") : (valueTypeValue ? "value type value" : "reference type value")));
    code = std::regex_replace(code, std::regex("_TYPE_KEY_"), (valueTypeKey ? "ValueTypeKey" : "ReferenceTypeKey"));
    code = std::regex_replace(code, std::regex("_TYPE_VALUE_"), (optional ? (valueTypeValue ? "OptionalValueTypeValue" : "OptionalReferenceTypeValue") : (valueTypeValue ? "ValueTypeValue" : "ReferenceTypeValue")));
    code = std::regex_replace(code, std::regex("_BASE_KEY_"), (valueTypeKey ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_BASE_VALUE_"), (valueTypeValue ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_KEY_"), "TKey");
    code = std::regex_replace(code, std::regex("_ARGS_VALUE_"), ((optional && valueTypeValue) ? "TValue?" : "TValue"));
    code = std::regex_replace(code, std::regex("_MODEL_KEY_CLONE_"), "modelKey.Clone() as TKeyModel");
    code = std::regex_replace(code, std::regex("_MODEL_KEY_DECL_"), "TKeyModel");
    code = std::regex_replace(code, std::regex("_MODEL_KEY_REF_"), (valueTypeKey ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_KEY_VALUE_"), "");
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_CLONE_"), (optional ? "new FieldModelOptional_MODEL_VALUE_REF_<TValue, TValueModel>(modelValue, buffer, offset)" : "modelValue.Clone() as TValueModel"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_DECL_"), (optional ? "FieldModelOptional_MODEL_VALUE_REF_<TValue, TValueModel>" : "TValueModel"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_REF_"), (valueTypeValue ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_VALUE_"), (optional ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelBase()
{
    std::string code = R"CODE(
    // Fast Binary Encoding final model value type
    public abstract class FinalModelValueType<T> : FieldModelBase
        where T : struct
    {
        protected FinalModelValueType(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public abstract long FBEAllocationSize(T value);

        // Clone the final model
        public abstract FinalModelValueType<T> Clone();

        // Check if the value is valid
        public abstract long Verify();

        // Get the value
        public abstract long Get(out T value);

        // Set the value
        public abstract long Set(T value);

        // Create final model of the given type
        public static FinalModelValueType<T> CreateFinalModel(BaseTypes type, Buffer buffer, long offset)
        {
            switch (type)
            {
                case BaseTypes.BOOL:
                    return new FinalModelBool(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.BYTE:
                    return new FinalModelByte(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.CHAR:
                    return new FinalModelChar(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.WCHAR:
                    return new FinalModelWChar(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.INT8:
                    return new FinalModelInt8(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.UINT8:
                    return new FinalModelUInt8(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.INT16:
                    return new FinalModelInt16(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.UINT16:
                    return new FinalModelUInt16(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.INT32:
                    return new FinalModelInt32(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.UINT32:
                    return new FinalModelUInt32(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.INT64:
                    return new FinalModelInt64(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.UINT64:
                    return new FinalModelUInt64(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.FLOAT:
                    return new FinalModelFloat(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.DOUBLE:
                    return new FinalModelDouble(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.DECIMAL:
                    return new FinalModelDecimal(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.UUID:
                    return new FinalModelUUID(buffer, offset) as FinalModelValueType<T>;
                case BaseTypes.TIMESTAMP:
                    return new FinalModelTimestamp(buffer, offset) as FinalModelValueType<T>;
                default:
                    Debug.Assert(false, "Unknown type!");
                    return null;
            }
        }
    }

    // Fast Binary Encoding final model reference type
    public abstract class FinalModelReferenceType<T> : FieldModelBase
        where T : class
    {
        protected FinalModelReferenceType(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public abstract long FBEAllocationSize(T value);

        // Clone the final model
        public abstract FinalModelReferenceType<T> Clone();

        // Check if the value is valid
        public abstract long Verify();

        // Get the value
        public abstract long Get(out T value);

        // Set the value
        public abstract long Set(T value);

        // Create final model of the given type
        public static FinalModelReferenceType<T> CreateFinalModel(BaseTypes type, Buffer buffer, long offset)
        {
            switch (type)
            {
                case BaseTypes.BYTES:
                    return new FinalModelBytes(buffer, offset) as FinalModelReferenceType<T>;
                case BaseTypes.STRING:
                    return new FinalModelString(buffer, offset) as FinalModelReferenceType<T>;
                default:
                    Debug.Assert(false, "Unknown type!");
                    return null;
            }
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModel(const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(
    // Fast Binary Encoding _TYPE_ final model
    public class FinalModel_NAME_ : FinalModelValueType<_TYPE_>
    {
        public FinalModel_NAME_(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public override long FBEAllocationSize(_TYPE_ value) { return FBESize; }

        // Get the final size
        public override long FBESize => _SIZE_;

        // Clone the final model
        public override FinalModelValueType<_TYPE_> Clone() { return new FinalModel_NAME_(_buffer, _offset); }

        // Check if the value is valid
        public override long Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return long.MaxValue;

            return FBESize;
        }

        // Get the value
        public override long Get(out _TYPE_ value)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = _DEFAULTS_;
                return 0;
            }

            value = Read_NAME_(FBEOffset);
            return FBESize;
        }

        // Set the value
        public override long Set(_TYPE_ value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return 0;

            Write(FBEOffset, _BASE_value);
            return FBESize;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelTimestamp()
{
    std::string code = R"CODE(
    // Fast Binary Encoding timestamp final model
    public class FinalModelTimestamp : FinalModelValueType<DateTime>
    {
        private const long UnixEpoch = 621355968000000000;

        public FinalModelTimestamp(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public override long FBEAllocationSize(DateTime value) { return FBESize; }

        // Get the final size
        public override long FBESize => 8;

        // Clone the final model
        public override FinalModelValueType<DateTime> Clone() { return new FinalModelTimestamp(_buffer, _offset); }

        // Check if the temestamp value is valid
        public override long Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return long.MaxValue;

            return FBESize;
        }

        // Get the timestamp value
        public override long Get(out DateTime value)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = new DateTime(UnixEpoch, DateTimeKind.Utc);
                return 0;
            }

            ulong ticks = ReadUInt64(FBEOffset) / 100;
            value = new DateTime((long)(UnixEpoch + ticks), DateTimeKind.Utc);
            return FBESize;
        }

        // Set the timestamp value
        public override long Set(DateTime value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return 0;

            ulong nanoseconds = (ulong)((value.Ticks - UnixEpoch) * 100);
            Write(FBEOffset, nanoseconds);
            return FBESize;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelBytes()
{
    std::string code = R"CODE(
    // Fast Binary Encoding bytes final model
    public class FinalModelBytes : FinalModelReferenceType<MemoryStream>
    {
        public FinalModelBytes(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public override long FBEAllocationSize(MemoryStream value) { return 4 + value.Length; }

        // Clone the final model
        public override FinalModelReferenceType<MemoryStream> Clone() { return new FinalModelBytes(_buffer, _offset); }

        // Check if the bytes value is valid
        public override long Verify()
        {
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return long.MaxValue;

            uint fbeBytesSize = ReadUInt32(FBEOffset);
            if ((_buffer.Offset + FBEOffset + 4 + fbeBytesSize) > _buffer.Size)
                return long.MaxValue;

            return 4 + fbeBytesSize;
        }

        // Get the bytes value
        public override long Get(out MemoryStream value)
        {
            value = new MemoryStream();

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            uint fbeBytesSize = ReadUInt32(FBEOffset);
            Debug.Assert(((_buffer.Offset + FBEOffset + 4 + fbeBytesSize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4 + fbeBytesSize) > _buffer.Size)
                return 4;

            var buffer = ReadBytes(FBEOffset + 4, fbeBytesSize);
            value.Write(buffer, 0, buffer.Length);
            return 4 + fbeBytesSize;
        }

        // Set the bytes value
        public override long Set(MemoryStream value)
        {
            Debug.Assert((value != null), "Invalid bytes value!");
            if (value == null)
                throw new ArgumentNullException(nameof(value), "Invalid bytes value!");

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            uint fbeBytesSize = (uint)value.Length;
            Debug.Assert(((_buffer.Offset + FBEOffset + 4 + fbeBytesSize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4 + fbeBytesSize) > _buffer.Size)
                return 4;

            Write(FBEOffset, fbeBytesSize);
            Write(FBEOffset + 4, value.GetBuffer(), 0, fbeBytesSize);
            return 4 + fbeBytesSize;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelString()
{
    std::string code = R"CODE(
    // Fast Binary Encoding string final model
    public class FinalModelString : FinalModelReferenceType<string>
    {
        public FinalModelString(Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public override long FBEAllocationSize(string value) { return 4 + Encoding.UTF8.GetMaxByteCount(value.Length); }

        // Clone the final model
        public override FinalModelReferenceType<string> Clone() { return new FinalModelString(_buffer, _offset); }

        // Check if the string value is valid
        public override long Verify()
        {
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return long.MaxValue;

            uint fbeStringSize = ReadUInt32(FBEOffset);
            if ((_buffer.Offset + FBEOffset + 4 + fbeStringSize) > _buffer.Size)
                return long.MaxValue;

            return 4 + fbeStringSize;
        }

        // Get the string value
        public override long Get(out string value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
            {
                value = "";
                return 0;
            }

            uint fbeStringSize = ReadUInt32(FBEOffset);
            Debug.Assert(((_buffer.Offset + FBEOffset + 4 + fbeStringSize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4 + fbeStringSize) > _buffer.Size)
            {
                value = "";
                return 4;
            }

            value = ReadString(FBEOffset + 4, fbeStringSize);
            return 4 + fbeStringSize;
        }

        // Set the string value
        public override long Set(string value)
        {
            Debug.Assert((value != null), "Invalid string value!");
            if (value == null)
                throw new ArgumentNullException(nameof(value), "Invalid string value!");

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            uint fbeStringSize = (uint)Write(FBEOffset + 4, value);
            Debug.Assert(((_buffer.Offset + FBEOffset + 4 + fbeStringSize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4 + fbeStringSize) > _buffer.Size)
                return 4;

            Write(FBEOffset, fbeStringSize);
            return 4 + fbeStringSize;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelOptional(bool valueType)
{
    std::string code = R"CODE(
    // Fast Binary Encoding final model optional (_NAME_)
    public class FinalModelOptional_TYPE_<T, TModel> : FieldModelBase
        where T : _BASE_
        where TModel : FinalModel_TYPE_<T>
    {
        public FinalModelOptional_TYPE_(TModel model, Buffer buffer, long offset) : base(buffer, offset)
        {
            Value = model.Clone() as TModel;
            // Reset the optional model offset
            if (Value != null)
                Value.FBEOffset = 0;
        }

        // Get the allocation size
        public long FBEAllocationSize(_ARGS_ optional) { return 1 + ((optional_HAS_VALUE_) ? Value.FBEAllocationSize(optional_VALUE_) : 0); }

        // Clone the final model
        public FinalModelOptional_TYPE_<T, TModel> Clone() { return new FinalModelOptional_TYPE_<T, TModel>(Value, _buffer, _offset); }

        //! Is the value present?
        public static implicit operator bool(FinalModelOptional_TYPE_<T, TModel> optional) { return !ReferenceEquals(optional, null) && optional.HasValue; }

        // Checks if the object contains a value
        public bool HasValue
        {
            get
            {
                if ((_buffer.Offset + FBEOffset + 1) > _buffer.Size)
                    return false;

                byte fbeHasValue = ReadUInt8(FBEOffset);
                return (fbeHasValue != 0);
            }
        }

        // Base final model value
        public TModel Value { get; }

        // Check if the optional value is valid
        public virtual long Verify()
        {
            if ((_buffer.Offset + FBEOffset + 1) > _buffer.Size)
                return long.MaxValue;

            byte fbeHasValue = ReadUInt8(FBEOffset);
            if (fbeHasValue == 0)
                return 1;

            _buffer.Shift(FBEOffset + 1);
            long fbeResult = Value.Verify();
            _buffer.Unshift(FBEOffset + 1);
            return 1 + fbeResult;
        }

        // Get the optional value
        public long Get(out _ARGS_ optional)
        {
            optional = null;

            Debug.Assert(((_buffer.Offset + FBEOffset + 1) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 1) > _buffer.Size)
                return 0;

            if (!HasValue)
                return 1;

            _buffer.Shift(FBEOffset + 1);
            long size = Value.Get(out var temp);
            optional = temp;
            _buffer.Unshift(FBEOffset + 1);
            return 1 + size;
        }

        // Set the optional value
        public long Set(_ARGS_ optional)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + 1) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 1) > _buffer.Size)
                return 0;

            byte fbeHasValue = (byte)((optional_HAS_VALUE_) ? 1 : 0);
            Write(FBEOffset, fbeHasValue);
            if (fbeHasValue == 0)
                return 1;

            _buffer.Shift(FBEOffset + 1);
            long size = 0;
            if (optional_HAS_VALUE_)
                size = Value.Set(optional_VALUE_);
            _buffer.Unshift(FBEOffset + 1);
            return 1 + size;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), (valueType ? "value type" : "reference type"));
    code = std::regex_replace(code, std::regex("_TYPE_"), (valueType ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_BASE_"), (valueType ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_"), (valueType ? "T?" : "T"));
    code = std::regex_replace(code, std::regex("_HAS_VALUE_"), (valueType ? ".HasValue" : " != null"));
    code = std::regex_replace(code, std::regex("_VALUE_"), (valueType ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelArray(bool valueType, bool optional)
{
    std::string code = R"CODE(
    // Fast Binary Encoding final model array (_NAME_)
    public class FinalModelArray_TYPE_<T, TModel> : FieldModelBase
        where T : _BASE_
        where TModel : FinalModel_MODEL_REF_<T>
    {
        private readonly _MODEL_DECL_ _model;
        private readonly long _size;

        public FinalModelArray_TYPE_(TModel model, Buffer buffer, long offset, long size) : base(buffer, offset)
        {
            _model = _MODEL_CLONE_;
            _size = size;
        }

        // Get the allocation size
        public long FBEAllocationSize(_ARGS_[] values)
        {
            long size = 0;
            for (long i = 0; (i < values.Length) && (i < _size); i++)
                size += _model.FBEAllocationSize(values[i]);
            return size;
        }
        public long FBEAllocationSize(List<_ARGS_> values)
        {
            long size = 0;
            for (long i = 0; (i < values.Count) && (i < _size); i++)
                size += _model.FBEAllocationSize(values[(int)i]);
            return size;
        }

        // Clone the final model
        public FinalModelArray_TYPE_<T, TModel> Clone() { return new FinalModelArray_TYPE_<T, TModel>(_model_MODEL_VALUE_, _buffer, _offset, _size); }

        // Check if the array is valid
        public virtual long Verify()
        {
            if ((_buffer.Offset + FBEOffset) > _buffer.Size)
                return long.MaxValue;

            long size = 0;
            _model.FBEOffset = FBEOffset;
            for (long i = _size; i-- > 0;)
            {
                long offset = _model.Verify();
                if (offset == long.MaxValue)
                    return long.MaxValue;
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Get the array
        public long Get(out _ARGS_[] values)
        {
            values = new _ARGS_[_size];

            Debug.Assert(((_buffer.Offset + FBEOffset) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset) > _buffer.Size)
                return 0;

            long size = 0;
            _model.FBEOffset = FBEOffset;
            for (long i = 0; i < _size; i++)
            {
                long offset = _model.Get(out values[i]);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Get the array as List
        public long Get(out List<_ARGS_> values)
        {
            values = new List<_ARGS_>((int)_size);

            Debug.Assert(((_buffer.Offset + FBEOffset) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset) > _buffer.Size)
                return 0;

            long size = 0;
            _model.FBEOffset = FBEOffset;
            for (long i = _size; i-- > 0;)
            {
                long offset = _model.Get(out var value);
                values.Add(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Set the array
        public long Set(_ARGS_[] values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset) > _buffer.Size)
                return 0;

            long size = 0;
            _model.FBEOffset = FBEOffset;
            for (long i = 0; (i < values.Length) && (i < _size); i++)
            {
                long offset = _model.Set(values[i]);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Set the array as List
        public long Set(List<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset) > _buffer.Size)
                return 0;

            long size = 0;
            _model.FBEOffset = FBEOffset;
            for (long i = 0; (i < values.Count) && (i < _size); i++)
            {
                long offset = _model.Set(values[(int)i]);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), (optional ? (valueType ? "optional value type" : "optional reference type") : (valueType ? "value type" : "reference type")));
    code = std::regex_replace(code, std::regex("_TYPE_"), (optional ? (valueType ? "OptionalValueType" : "OptionalReferenceType") : (valueType ? "ValueType" : "ReferenceType")));
    code = std::regex_replace(code, std::regex("_BASE_"), (valueType ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_"), ((optional && valueType) ? "T?" : "T"));
    code = std::regex_replace(code, std::regex("_MODEL_CLONE_"), (optional ? "new FinalModelOptional_MODEL_REF_<T, TModel>(model, buffer, offset)" : "model.Clone() as TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_DECL_"), (optional ? "FinalModelOptional_MODEL_REF_<T, TModel>" : "TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_REF_"), (valueType ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_"), (optional ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelVector(bool valueType, bool optional)
{
    std::string code = R"CODE(
    // Fast Binary Encoding final model vector (_NAME_)
    public class FinalModelVector_TYPE_<T, TModel> : FieldModelBase
        where T : _BASE_
        where TModel : FinalModel_MODEL_REF_<T>
    {
        private readonly _MODEL_DECL_ _model;

        public FinalModelVector_TYPE_(TModel model, Buffer buffer, long offset) : base(buffer, offset)
        {
            _model = _MODEL_CLONE_;
        }

        // Get the allocation size
        public long FBEAllocationSize(List<_ARGS_> values)
        {
            long size = 4;
            foreach (var value in values)
                size += _model.FBEAllocationSize(value);
            return size;
        }
        public long FBEAllocationSize(LinkedList<_ARGS_> values)
        {
            long size = 4;
            foreach (var value in values)
                size += _model.FBEAllocationSize(value);
            return size;
        }
        public long FBEAllocationSize(HashSet<_ARGS_> values)
        {
            long size = 4;
            foreach (var value in values)
                size += _model.FBEAllocationSize(value);
            return size;
        }

        // Clone the final model
        public FinalModelVector_TYPE_<T, TModel> Clone() { return new FinalModelVector_TYPE_<T, TModel>(_model_MODEL_VALUE_, _buffer, _offset); }

        // Check if the vector is valid
        public virtual long Verify()
        {
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return long.MaxValue;

            uint fbeVectorSize = ReadUInt32(FBEOffset);

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            for (uint i = fbeVectorSize; i-- > 0;)
            {
                long offset = _model.Verify();
                if (offset == long.MaxValue)
                    return long.MaxValue;
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Get the vector as List
        public long Get(out List<_ARGS_> values)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
            {
                values = new List<_ARGS_>();
                return 0;
            }

            long fbeVectorSize = ReadUInt32(FBEOffset);
            if (fbeVectorSize == 0)
            {
                values = new List<_ARGS_>();
                return 4;
            }

            values = new List<_ARGS_>((int)fbeVectorSize);

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            for (long i = fbeVectorSize; i-- > 0;)
            {
                long offset = _model.Get(out var value);
                values.Add(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Get the vector as LinkedList
        public long Get(out LinkedList<_ARGS_> values)
        {
            values = new LinkedList<_ARGS_>();

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            long fbeVectorSize = ReadUInt32(FBEOffset);
            if (fbeVectorSize == 0)
                return 4;

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            for (long i = fbeVectorSize; i-- > 0;)
            {
                long offset = _model.Get(out var value);
                values.AddLast(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Get the vector as HashSet
        public long Get(out HashSet<_ARGS_> values)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
            {
                values = new HashSet<_ARGS_>();
                return 0;
            }

            long fbeVectorSize = ReadUInt32(FBEOffset);
            if (fbeVectorSize == 0)
            {
                values = new HashSet<_ARGS_>();
                return 4;
            }

            values = new HashSet<_ARGS_>((int)fbeVectorSize);

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            for (long i = fbeVectorSize; i-- > 0;)
            {
                long offset = _model.Get(out var value);
                values.Add(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Set the vector as List
        public long Set(List<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            Write(FBEOffset, (uint)values.Count);

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            foreach (var value in values)
            {
                long offset = _model.Set(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Set the vector as LinkedList
        public long Set(LinkedList<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            Write(FBEOffset, (uint)values.Count);

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            foreach (var value in values)
            {
                long offset = _model.Set(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }

        // Set the vector as HashSet
        public long Set(HashSet<_ARGS_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return 0;

            Write(FBEOffset, (uint)values.Count);

            long size = 4;
            _model.FBEOffset = FBEOffset + 4;
            foreach (var value in values)
            {
                long offset = _model.Set(value);
                _model.FBEShift(offset);
                size += offset;
            }
            return size;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), (optional ? (valueType ? "optional value type" : "optional reference type") : (valueType ? "value type" : "reference type")));
    code = std::regex_replace(code, std::regex("_TYPE_"), (optional ? (valueType ? "OptionalValueType" : "OptionalReferenceType") : (valueType ? "ValueType" : "ReferenceType")));
    code = std::regex_replace(code, std::regex("_BASE_"), (valueType ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_"), ((optional && valueType) ? "T?" : "T"));
    code = std::regex_replace(code, std::regex("_MODEL_CLONE_"), (optional ? "new FinalModelOptional_MODEL_REF_<T, TModel>(model, buffer, offset)" : "model.Clone() as TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_DECL_"), (optional ? "FinalModelOptional_MODEL_REF_<T, TModel>" : "TModel"));
    code = std::regex_replace(code, std::regex("_MODEL_REF_"), (valueType ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_"), (optional ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEFinalModelMap(bool valueTypeKey, bool valueTypeValue, bool optional)
{
    std::string code = R"CODE(
    // Fast Binary Encoding final model map (_NAME_KEY_, _NAME_VALUE_)
    public class FinalModelMap_TYPE_KEY__TYPE_VALUE_<TKey, TKeyModel, TValue, TValueModel> : FieldModelBase
        where TKey : _BASE_KEY_
        where TKeyModel : FinalModel_MODEL_KEY_REF_<TKey>
        where TValue : _BASE_VALUE_
        where TValueModel : FinalModel_MODEL_VALUE_REF_<TValue>
    {
        private readonly _MODEL_KEY_DECL_ _modelKey;
        private readonly _MODEL_VALUE_DECL_ _modelValue;

        public FinalModelMap_TYPE_KEY__TYPE_VALUE_(TKeyModel modelKey, TValueModel modelValue, Buffer buffer, long offset) : base(buffer, offset)
        {
            _modelKey = _MODEL_KEY_CLONE_;
            _modelValue = _MODEL_VALUE_CLONE_;
        }

        // Get the allocation size
        public long FBEAllocationSize(Dictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            long size = 4;
            foreach (var value in values)
            {
                size += _modelKey.FBEAllocationSize(value.Key);
                size += _modelValue.FBEAllocationSize(value.Value);
            }
            return size;
        }
        public long FBEAllocationSize(SortedDictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            long size = 4;
            foreach (var value in values)
            {
                size += _modelKey.FBEAllocationSize(value.Key);
                size += _modelValue.FBEAllocationSize(value.Value);
            }
            return size;
        }

        // Clone the final model
        public FinalModelMap_TYPE_KEY__TYPE_VALUE_<TKey, TKeyModel, TValue, TValueModel> Clone() { return new FinalModelMap_TYPE_KEY__TYPE_VALUE_<TKey, TKeyModel, TValue, TValueModel>(_modelKey_MODEL_KEY_VALUE_, _modelValue_MODEL_VALUE_VALUE_, _buffer, _offset); }

        // Check if the map is valid
        public virtual long Verify()
        {
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return long.MaxValue;

            uint fbeMapSize = ReadUInt32(FBEOffset);

            long size = 4;
            _modelKey.FBEOffset = FBEOffset + 4;
            _modelValue.FBEOffset = FBEOffset + 4;
            for (uint i = fbeMapSize; i-- > 0;)
            {
                long offsetKey = _modelKey.Verify();
                if (offsetKey == long.MaxValue)
                    return long.MaxValue;
                _modelKey.FBEShift(offsetKey);
                _modelValue.FBEShift(offsetKey);
                size += offsetKey;
                long offsetValue = _modelValue.Verify();
                if (offsetValue == long.MaxValue)
                    return long.MaxValue;
                _modelKey.FBEShift(offsetValue);
                _modelValue.FBEShift(offsetValue);
                size += offsetValue;
            }
            return size;
        }

        // Get the map as Dictionary
        public long Get(out Dictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
            {
                values = new Dictionary<_ARGS_KEY_, _ARGS_VALUE_>();
                return 0;
            }

            long fbeMapSize = ReadUInt32(FBEOffset);
            if (fbeMapSize == 0)
            {
                values = new Dictionary<_ARGS_KEY_, _ARGS_VALUE_>();
                return 4;
            }

            values = new Dictionary<_ARGS_KEY_, _ARGS_VALUE_>((int)fbeMapSize);

            long size = 4;
            _modelKey.FBEOffset = FBEOffset + 4;
            _modelValue.FBEOffset = FBEOffset + 4;
            for (long i = fbeMapSize; i-- > 0;)
            {
                long offsetKey = _modelKey.Get(out var key);
                _modelKey.FBEShift(offsetKey);
                _modelValue.FBEShift(offsetKey);
                long offsetValue = _modelValue.Get(out var value);
                _modelKey.FBEShift(offsetValue);
                _modelValue.FBEShift(offsetValue);
                values.Add(key, value);
                size += offsetKey + offsetValue;
            }
            return size;
        }

        // Get the map as SortedDictionary
        public long Get(out SortedDictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            values = new SortedDictionary<_ARGS_KEY_, _ARGS_VALUE_>();

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            long fbeMapSize = ReadUInt32(FBEOffset);
            if (fbeMapSize == 0)
                return 4;

            long size = 4;
            _modelKey.FBEOffset = FBEOffset + 4;
            _modelValue.FBEOffset = FBEOffset + 4;
            for (long i = fbeMapSize; i-- > 0;)
            {
                long offsetKey = _modelKey.Get(out var key);
                _modelKey.FBEShift(offsetKey);
                _modelValue.FBEShift(offsetKey);
                long offsetValue = _modelValue.Get(out var value);
                _modelKey.FBEShift(offsetValue);
                _modelValue.FBEShift(offsetValue);
                values.Add(key, value);
                size += offsetKey + offsetValue;
            }
            return size;
        }

        // Set the map as Dictionary
        public long Set(Dictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            Write(FBEOffset, (uint)values.Count);

            long size = 4;
            _modelKey.FBEOffset = FBEOffset + 4;
            _modelValue.FBEOffset = FBEOffset + 4;
            foreach (var value in values)
            {
                long offsetKey = _modelKey.Set(value.Key);
                _modelKey.FBEShift(offsetKey);
                _modelValue.FBEShift(offsetKey);
                long offsetValue = _modelValue.Set(value.Value);
                _modelKey.FBEShift(offsetValue);
                _modelValue.FBEShift(offsetValue);
                size += offsetKey + offsetValue;
            }
            return size;
        }

        // Set the map as SortedDictionary
        public long Set(SortedDictionary<_ARGS_KEY_, _ARGS_VALUE_> values)
        {
            Debug.Assert((values != null), "Invalid values parameter!");
            if (values == null)
                throw new ArgumentNullException(nameof(values), "Invalid values parameter!");

            Debug.Assert(((_buffer.Offset + FBEOffset + 4) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + 4) > _buffer.Size)
                return 0;

            Write(FBEOffset, (uint)values.Count);

            long size = 4;
            _modelKey.FBEOffset = FBEOffset + 4;
            _modelValue.FBEOffset = FBEOffset + 4;
            foreach (var value in values)
            {
                long offsetKey = _modelKey.Set(value.Key);
                _modelKey.FBEShift(offsetKey);
                _modelValue.FBEShift(offsetKey);
                long offsetValue = _modelValue.Set(value.Value);
                _modelKey.FBEShift(offsetValue);
                _modelValue.FBEShift(offsetValue);
                size += offsetKey + offsetValue;
            }
            return size;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_KEY_"), (valueTypeKey ? "value type key" : "reference type key"));
    code = std::regex_replace(code, std::regex("_NAME_VALUE_"), (optional ? (valueTypeValue ? "optional value type value" : "optional reference type value") : (valueTypeValue ? "value type value" : "reference type value")));
    code = std::regex_replace(code, std::regex("_TYPE_KEY_"), (valueTypeKey ? "ValueTypeKey" : "ReferenceTypeKey"));
    code = std::regex_replace(code, std::regex("_TYPE_VALUE_"), (optional ? (valueTypeValue ? "OptionalValueTypeValue" : "OptionalReferenceTypeValue") : (valueTypeValue ? "ValueTypeValue" : "ReferenceTypeValue")));
    code = std::regex_replace(code, std::regex("_BASE_KEY_"), (valueTypeKey ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_BASE_VALUE_"), (valueTypeValue ? "struct" : "class"));
    code = std::regex_replace(code, std::regex("_ARGS_KEY_"), "TKey");
    code = std::regex_replace(code, std::regex("_ARGS_VALUE_"), ((optional && valueTypeValue) ? "TValue?" : "TValue"));
    code = std::regex_replace(code, std::regex("_MODEL_KEY_CLONE_"), "modelKey.Clone() as TKeyModel");
    code = std::regex_replace(code, std::regex("_MODEL_KEY_DECL_"), "TKeyModel");
    code = std::regex_replace(code, std::regex("_MODEL_KEY_REF_"), (valueTypeKey ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_KEY_VALUE_"), "");
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_CLONE_"), (optional ? "new FinalModelOptional_MODEL_VALUE_REF_<TValue, TValueModel>(modelValue, buffer, offset)" : "modelValue.Clone() as TValueModel"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_DECL_"), (optional ? "FinalModelOptional_MODEL_VALUE_REF_<TValue, TValueModel>" : "TValueModel"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_REF_"), (valueTypeValue ? "ValueType" : "ReferenceType"));
    code = std::regex_replace(code, std::regex("_MODEL_VALUE_VALUE_"), (optional ? ".Value" : ""));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBESender()
{
    std::string code = R"CODE(
    // Fast Binary Encoding base sender listener interface
    public interface ISenderListener
    {
        // Send message handler
        long OnSend(byte[] buffer, long offset, long size) { return size; }
        // Send log message handler
        void OnSendLog(string message) {}
    }

    // Fast Binary Encoding base sender
    public abstract class Sender : ISenderListener
    {
        // Bytes buffer
        public Buffer Buffer { get; }
        // Logging flag
        public bool Logging { get; set; }
        // Final protocol flag
        public bool Final { get; }

        protected Sender(bool final) { Buffer = new Buffer(); Final = final; }
        protected Sender(Buffer buffer, bool final) { Buffer = buffer; Final = final; }

        // Reset the sender buffer
        public void Reset() { Buffer.Reset(); }

        // Send serialized buffer.
        // Direct call of the method requires knowledge about internals of FBE models serialization.
        // Use it with care!
        public long SendSerialized(ISenderListener listener, long serialized)
        {
            Debug.Assert((serialized > 0), "Invalid size of the serialized buffer!");
            if (serialized == 0)
                return 0;

            // Shift the send buffer
            Buffer.Shift(serialized);

            // Send the value
            long sent = listener.OnSend(Buffer.Data, 0, Buffer.Size);
            Buffer.Remove(0, sent);
            return sent;
        }
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEReceiver()
{
    std::string code = R"CODE(
    // Fast Binary Encoding base receiver listener interface
    public interface IReceiverListener
    {
        // Receive log message handler
        void OnReceiveLog(string message) {}
    }

    // Fast Binary Encoding base receiver
    public abstract class Receiver : IReceiverListener
    {
        // Bytes buffer
        public Buffer Buffer { get; private set; }
        // Logging flag
        public bool Logging { get; set; }
        // Final protocol flag
        public bool Final { get; }

        protected Receiver(bool final) { Buffer = new Buffer(); Final = final; }
        protected Receiver(Buffer buffer, bool final) { Buffer = buffer; Final = final; }

        // Reset the receiver buffer
        public void Reset() { Buffer.Reset(); }

        // Receive data
        public void Receive(Buffer buffer) { Receive(buffer.Data, 0, buffer.Size); }
        public void Receive(byte[] buffer) { Receive(buffer, 0, buffer.Length); }
        public void Receive(byte[] buffer, long offset, long size)
        {
            Debug.Assert((buffer != null), "Invalid buffer!");
            if (buffer == null)
                throw new ArgumentException("Invalid buffer!", nameof(buffer));
            Debug.Assert(((offset + size) <= buffer.Length), "Invalid offset & size!");
            if ((offset + size) > buffer.Length)
                throw new ArgumentException("Invalid offset & size!", nameof(offset));

            if (size == 0)
                return;

            // Storage buffer
            long offset0 = Buffer.Offset;
            long offset1 = Buffer.Size;
            long size1 = Buffer.Size;

            // Receive buffer
            long offset2 = 0;
            long size2 = size;

            // While receive buffer is available to handle...
            while (offset2 < size2)
            {
                byte[] messageBuffer = null;
                long messageOffset = 0;
                long messageSize = 0;

                // Try to receive message size
                bool messageSizeCopied = false;
                bool messageSizeFound = false;
                while (!messageSizeFound)
                {
                    // Look into the storage buffer
                    if (offset0 < size1)
                    {
                        long count = Math.Min(size1 - offset0, 4);
                        if (count == 4)
                        {
                            messageSizeCopied = true;
                            messageSizeFound = true;
                            messageSize = Buffer.ReadUInt32(Buffer.Data, offset0);
                            offset0 += 4;
                            break;
                        }
                        else
                        {
                            // Fill remaining data from the receive buffer
                            if (offset2 < size2)
                            {
                                count = Math.Min(size2 - offset2, 4 - count);

                                // Allocate and refresh the storage buffer
                                Buffer.Allocate(count);
                                size1 += count;

                                Array.Copy(buffer, offset + offset2, Buffer.Data, offset1, count);
                                offset1 += count;
                                offset2 += count;
                                continue;
                            }
                            else
                                break;
                        }
                    }

                    // Look into the receive buffer
                    if (offset2 < size2)
                    {
                        long count = Math.Min(size2 - offset2, 4);
                        if (count == 4)
                        {
                            messageSizeFound = true;
                            messageSize = Buffer.ReadUInt32(buffer, offset + offset2);
                            offset2 += 4;
                            break;
                        }
                        else
                        {
                            // Allocate and refresh the storage buffer
                            Buffer.Allocate(count);
                            size1 += count;

                            Array.Copy(buffer, offset + offset2, Buffer.Data, offset1, count);
                            offset1 += count;
                            offset2 += count;
                            continue;
                        }
                    }
                    else
                        break;
                }

                if (!messageSizeFound)
                    return;

                // Check the message full size
                long minSize = Final ? (4 + 4) : (4 + 4 + 4 + 4);
                Debug.Assert((messageSize >= minSize), "Invalid receive data!");
                if (messageSize < minSize)
                    return;

                // Try to receive message body
                bool messageFound = false;
                while (!messageFound)
                {
                    // Look into the storage buffer
                    if (offset0 < size1)
                    {
                        long count = Math.Min(size1 - offset0, messageSize - 4);
                        if (count == (messageSize - 4))
                        {
                            messageFound = true;
                            messageBuffer = Buffer.Data;
                            messageOffset = offset0 - 4;
                            offset0 += messageSize - 4;
                            break;
                        }
                        else
                        {
                            // Fill remaining data from the receive buffer
                            if (offset2 < size2)
                            {
                                // Copy message size into the storage buffer
                                if (!messageSizeCopied)
                                {
                                    // Allocate and refresh the storage buffer
                                    Buffer.Allocate(4);
                                    size1 += 4;

                                    Buffer.Write(Buffer.Data, offset0, (uint)messageSize);
                                    offset0 += 4;
                                    offset1 += 4;

                                    messageSizeCopied = true;
                                }

                                count = Math.Min(size2 - offset2, messageSize - 4 - count);

                                // Allocate and refresh the storage buffer
                                Buffer.Allocate(count);
                                size1 += count;

                                Array.Copy(buffer, offset + offset2, Buffer.Data, offset1, count);
                                offset1 += count;
                                offset2 += count;
                                continue;
                            }
                            else
                                break;
                        }
                    }

                    // Look into the receive buffer
                    if (offset2 < size2)
                    {
                        long count = Math.Min(size2 - offset2, messageSize - 4);
                        if (!messageSizeCopied && (count == (messageSize - 4)))
                        {
                            messageFound = true;
                            messageBuffer = buffer;
                            messageOffset = offset + offset2 - 4;
                            offset2 += messageSize - 4;
                            break;
                        }
                        else
                        {
                            // Copy message size into the storage buffer
                            if (!messageSizeCopied)
                            {
                                // Allocate and refresh the storage buffer
                                Buffer.Allocate(4);
                                size1 += 4;

                                Buffer.Write(Buffer.Data, offset0, (uint)messageSize);
                                offset0 += 4;
                                offset1 += 4;

                                messageSizeCopied = true;
                            }

                            // Allocate and refresh the storage buffer
                            Buffer.Allocate(count);
                            size1 += count;

                            Array.Copy(buffer, offset + offset2, Buffer.Data, offset1, count);
                            offset1 += count;
                            offset2 += count;
                            continue;
                        }
                    }
                    else
                        break;
                }

                if (!messageFound)
                {
                    // Copy message size into the storage buffer
                    if (!messageSizeCopied)
                    {
                        // Allocate and refresh the storage buffer
                        Buffer.Allocate(4);
                        size1 += 4;

                        Buffer.Write(Buffer.Data, offset0, (uint)messageSize);
                        offset0 += 4;
                        offset1 += 4;

                        messageSizeCopied = true;
                    }
                    return;
                }

                uint fbeStructSize;
                uint fbeStructType;

                // Read the message parameters
                if (Final)
                {
                    fbeStructSize = Buffer.ReadUInt32(messageBuffer, messageOffset);
                    fbeStructType = Buffer.ReadUInt32(messageBuffer, messageOffset + 4);
                }
                else
                {
                    uint fbeStructOffset = Buffer.ReadUInt32(messageBuffer, messageOffset + 4);
                    fbeStructSize = Buffer.ReadUInt32(messageBuffer, messageOffset + fbeStructOffset);
                    fbeStructType = Buffer.ReadUInt32(messageBuffer, messageOffset + fbeStructOffset + 4);
                }

                // Handle the message
                OnReceive(fbeStructType, messageBuffer, messageOffset, messageSize);

                // Reset the storage buffer
                Buffer.Reset();

                // Refresh the storage buffer
                offset0 = Buffer.Offset;
                offset1 = Buffer.Size;
                size1 = Buffer.Size;
            }
        }

        // Receive message handler
        internal abstract bool OnReceive(long type, byte[] buffer, long offset, long size);
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEClient()
{
    std::string code = R"CODE(
    // Fast Binary Encoding base client listener interface
    public interface IClientListener : ISenderListener, IReceiverListener
    {
    }

    // Fast Binary Encoding base client
    public abstract class Client : IClientListener
    {
        // Send bytes buffer
        public Buffer SendBuffer { get; }
        // Receive bytes buffer
        public Buffer ReceiveBuffer { get; }
        // Logging flag
        public bool Logging { get; set; }
        // Final protocol flag
        public bool Final { get; }

        // Client mutex lock
        protected Mutex Lock { get; }
        // Client timestamp
        protected DateTime Timestamp { get; set; }

        protected Client(bool final) : this(new Buffer(), new Buffer(), final) {}
        protected Client(Buffer sendBuffer, Buffer receiveBuffer, bool final) { SendBuffer = sendBuffer; ReceiveBuffer = receiveBuffer; Final = final; Lock = new Mutex(); Timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); }

        // Reset the client buffers
        public void Reset()
        {
            lock (Lock)
            {
                ResetRequests();
            }
        }

        // Reset client requests
        internal virtual void ResetRequests()
        {
            SendBuffer.Reset();
            ReceiveBuffer.Reset();
        }

        // Watchdog for timeouts
        public void Watchdog(DateTime utc)
        {
            lock (Lock)
            {
                WatchdogRequests(utc);
            }
        }

        // Watchdog client requests for timeouts
        internal virtual void WatchdogRequests(DateTime utc)
        {
        }

        // Send serialized buffer.
        // Direct call of the method requires knowledge about internals of FBE models serialization.
        // Use it with care!
        public long SendSerialized(ISenderListener listener, long serialized)
        {
            Debug.Assert((serialized > 0), "Invalid size of the serialized buffer!");
            if (serialized == 0)
                return 0;

            // Shift the send buffer
            SendBuffer.Shift(serialized);

            // Send the value
            long sent = listener.OnSend(SendBuffer.Data, 0, SendBuffer.Size);
            SendBuffer.Remove(0, sent);
            return sent;
        }

        // Receive data
        public void Receive(Buffer buffer) { Receive(buffer.Data, 0, buffer.Size); }
        public void Receive(byte[] buffer) { Receive(buffer, 0, buffer.Length); }
        public void Receive(byte[] buffer, long offset, long size)
        {
            Debug.Assert((buffer != null), "Invalid buffer!");
            if (buffer == null)
                throw new ArgumentException("Invalid buffer!", nameof(buffer));
            Debug.Assert(((offset + size) <= buffer.Length), "Invalid offset & size!");
            if ((offset + size) > buffer.Length)
                throw new ArgumentException("Invalid offset & size!", nameof(offset));

            if (size == 0)
                return;

            // Storage buffer
            long offset0 = ReceiveBuffer.Offset;
            long offset1 = ReceiveBuffer.Size;
            long size1 = ReceiveBuffer.Size;

            // Receive buffer
            long offset2 = 0;
            long size2 = size;

            // While receive buffer is available to handle...
            while (offset2 < size2)
            {
                byte[] messageBuffer = null;
                long messageOffset = 0;
                long messageSize = 0;

                // Try to receive message size
                bool messageSizeCopied = false;
                bool messageSizeFound = false;
                while (!messageSizeFound)
                {
                    // Look into the storage buffer
                    if (offset0 < size1)
                    {
                        long count = Math.Min(size1 - offset0, 4);
                        if (count == 4)
                        {
                            messageSizeCopied = true;
                            messageSizeFound = true;
                            messageSize = Buffer.ReadUInt32(ReceiveBuffer.Data, offset0);
                            offset0 += 4;
                            break;
                        }
                        else
                        {
                            // Fill remaining data from the receive buffer
                            if (offset2 < size2)
                            {
                                count = Math.Min(size2 - offset2, 4 - count);

                                // Allocate and refresh the storage buffer
                                ReceiveBuffer.Allocate(count);
                                size1 += count;

                                Array.Copy(buffer, offset + offset2, ReceiveBuffer.Data, offset1, count);
                                offset1 += count;
                                offset2 += count;
                                continue;
                            }
                            else
                                break;
                        }
                    }

                    // Look into the receive buffer
                    if (offset2 < size2)
                    {
                        long count = Math.Min(size2 - offset2, 4);
                        if (count == 4)
                        {
                            messageSizeFound = true;
                            messageSize = Buffer.ReadUInt32(buffer, offset + offset2);
                            offset2 += 4;
                            break;
                        }
                        else
                        {
                            // Allocate and refresh the storage buffer
                            ReceiveBuffer.Allocate(count);
                            size1 += count;

                            Array.Copy(buffer, offset + offset2, ReceiveBuffer.Data, offset1, count);
                            offset1 += count;
                            offset2 += count;
                            continue;
                        }
                    }
                    else
                        break;
                }

                if (!messageSizeFound)
                    return;

                // Check the message full size
                long minSize = Final ? (4 + 4) : (4 + 4 + 4 + 4);
                Debug.Assert((messageSize >= minSize), "Invalid receive data!");
                if (messageSize < minSize)
                    return;

                // Try to receive message body
                bool messageFound = false;
                while (!messageFound)
                {
                    // Look into the storage buffer
                    if (offset0 < size1)
                    {
                        long count = Math.Min(size1 - offset0, messageSize - 4);
                        if (count == (messageSize - 4))
                        {
                            messageFound = true;
                            messageBuffer = ReceiveBuffer.Data;
                            messageOffset = offset0 - 4;
                            offset0 += messageSize - 4;
                            break;
                        }
                        else
                        {
                            // Fill remaining data from the receive buffer
                            if (offset2 < size2)
                            {
                                // Copy message size into the storage buffer
                                if (!messageSizeCopied)
                                {
                                    // Allocate and refresh the storage buffer
                                    ReceiveBuffer.Allocate(4);
                                    size1 += 4;

                                    Buffer.Write(ReceiveBuffer.Data, offset0, (uint)messageSize);
                                    offset0 += 4;
                                    offset1 += 4;

                                    messageSizeCopied = true;
                                }

                                count = Math.Min(size2 - offset2, messageSize - 4 - count);

                                // Allocate and refresh the storage buffer
                                ReceiveBuffer.Allocate(count);
                                size1 += count;

                                Array.Copy(buffer, offset + offset2, ReceiveBuffer.Data, offset1, count);
                                offset1 += count;
                                offset2 += count;
                                continue;
                            }
                            else
                                break;
                        }
                    }

                    // Look into the receive buffer
                    if (offset2 < size2)
                    {
                        long count = Math.Min(size2 - offset2, messageSize - 4);
                        if (!messageSizeCopied && (count == (messageSize - 4)))
                        {
                            messageFound = true;
                            messageBuffer = buffer;
                            messageOffset = offset + offset2 - 4;
                            offset2 += messageSize - 4;
                            break;
                        }
                        else
                        {
                            // Copy message size into the storage buffer
                            if (!messageSizeCopied)
                            {
                                // Allocate and refresh the storage buffer
                                ReceiveBuffer.Allocate(4);
                                size1 += 4;

                                Buffer.Write(ReceiveBuffer.Data, offset0, (uint)messageSize);
                                offset0 += 4;
                                offset1 += 4;

                                messageSizeCopied = true;
                            }

                            // Allocate and refresh the storage buffer
                            ReceiveBuffer.Allocate(count);
                            size1 += count;

                            Array.Copy(buffer, offset + offset2, ReceiveBuffer.Data, offset1, count);
                            offset1 += count;
                            offset2 += count;
                            continue;
                        }
                    }
                    else
                        break;
                }

                if (!messageFound)
                {
                    // Copy message size into the storage buffer
                    if (!messageSizeCopied)
                    {
                        // Allocate and refresh the storage buffer
                        ReceiveBuffer.Allocate(4);
                        size1 += 4;

                        Buffer.Write(ReceiveBuffer.Data, offset0, (uint)messageSize);
                        offset0 += 4;
                        offset1 += 4;

                        messageSizeCopied = true;
                    }
                    return;
                }

                uint fbeStructSize;
                uint fbeStructType;

                // Read the message parameters
                if (Final)
                {
                    fbeStructSize = Buffer.ReadUInt32(messageBuffer, messageOffset);
                    fbeStructType = Buffer.ReadUInt32(messageBuffer, messageOffset + 4);
                }
                else
                {
                    uint fbeStructOffset = Buffer.ReadUInt32(messageBuffer, messageOffset + 4);
                    fbeStructSize = Buffer.ReadUInt32(messageBuffer, messageOffset + fbeStructOffset);
                    fbeStructType = Buffer.ReadUInt32(messageBuffer, messageOffset + fbeStructOffset + 4);
                }

                // Handle the message
                OnReceive(fbeStructType, messageBuffer, messageOffset, messageSize);

                // Reset the storage buffer
                ReceiveBuffer.Reset();

                // Refresh the storage buffer
                offset0 = ReceiveBuffer.Offset;
                offset1 = ReceiveBuffer.Size;
                size1 = ReceiveBuffer.Size;
            }
        }

        // Receive message handler
        internal abstract bool OnReceive(long type, byte[] buffer, long offset, long size);
    }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBEJson()
{
    std::string code = R"CODE(
#if UTF8JSON
    public sealed class ByteArrayFormatter : IJsonFormatter<byte[]>
    {
        public void Serialize(ref JsonWriter writer, byte[] value, IJsonFormatterResolver formatterResolver)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            writer.WriteBeginArray();

            if (value.Length != 0)
                writer.WriteByte(value[0]);
            for (int i = 1; i < value.Length; i++)
            {
                writer.WriteValueSeparator();
                writer.WriteByte(value[i]);
            }

            writer.WriteEndArray();
        }

        public byte[] Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
        {
            if (reader.ReadIsNull())
                return null;

            reader.ReadIsBeginArrayWithVerify();
            var array = new byte[4];
            var count = 0;
            while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
            {
                if (array.Length < count)
                    Array.Resize(ref array, count * 2);
                array[count - 1] = reader.ReadByte();
            }

            Array.Resize(ref array, count);
            return array;
        }
    }

    public class BytesFormatter : IJsonFormatter<MemoryStream>
    {
        public void Serialize(ref JsonWriter writer, MemoryStream value, IJsonFormatterResolver jsonFormatterResolver)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            writer.WriteString(System.Convert.ToBase64String(value.GetBuffer()));
        }

        public MemoryStream Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            if (reader.ReadIsNull())
                return null;

            var buffer = System.Convert.FromBase64String(reader.ReadString());
            return new MemoryStream(buffer, 0, buffer.Length, true, true);
        }
    }

    public class CharFormatter : IJsonFormatter<Char>
    {
        public void Serialize(ref JsonWriter writer, Char value, IJsonFormatterResolver jsonFormatterResolver)
        {
            writer.WriteUInt32(value);
        }

        public Char Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            return (char)reader.ReadUInt32();
        }
    }

    public class CharNullableFormatter : IJsonFormatter<Char?>
    {
        public void Serialize(ref JsonWriter writer, Char? value, IJsonFormatterResolver jsonFormatterResolver)
        {
            if (!value.HasValue)
            {
                writer.WriteNull();
                return;
            }

            writer.WriteUInt32(value.Value);
        }

        public Char? Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            if (reader.ReadIsNull())
                return null;

            return (char)reader.ReadUInt32();
        }
    }

    public class DateTimeFormatter : IJsonFormatter<DateTime>
    {
        private const long UnixEpoch = 621355968000000000;

        public void Serialize(ref JsonWriter writer, DateTime value, IJsonFormatterResolver jsonFormatterResolver)
        {
            ulong nanoseconds = (ulong)((value.Ticks - UnixEpoch) * 100);
            writer.WriteUInt64(nanoseconds);
        }

        public DateTime Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            ulong ticks = (ulong)(long)reader.ReadUInt64() / 100;
            return new DateTime((long)(UnixEpoch + ticks), DateTimeKind.Utc);
        }
    }

    public class DateTimeNullableFormatter : IJsonFormatter<DateTime?>
    {
        private const long UnixEpoch = 621355968000000000;

        public void Serialize(ref JsonWriter writer, DateTime? value, IJsonFormatterResolver jsonFormatterResolver)
        {
            if (!value.HasValue)
            {
                writer.WriteNull();
                return;
            }

            ulong nanoseconds = (ulong)((value.Value.Ticks - UnixEpoch) * 100);
            writer.WriteUInt64(nanoseconds);
        }

        public DateTime? Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            if (reader.ReadIsNull())
                return null;

            ulong ticks = (ulong)(long)reader.ReadUInt64() / 100;
            return new DateTime((long)(UnixEpoch + ticks), DateTimeKind.Utc);
        }
    }

    public class DecimalFormatter : IJsonFormatter<Decimal>
    {
        public void Serialize(ref JsonWriter writer, Decimal value, IJsonFormatterResolver jsonFormatterResolver)
        {
            writer.WriteString(value.ToString(CultureInfo.InvariantCulture));
        }

        public Decimal Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            return Decimal.Parse(reader.ReadString(), CultureInfo.InvariantCulture);
        }
    }

    public class GuidFormatter : IJsonFormatter<Guid>
    {
        public void Serialize(ref JsonWriter writer, Guid value, IJsonFormatterResolver jsonFormatterResolver)
        {
            writer.WriteString(value.ToString());
        }

        public Guid Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            return Guid.Parse(reader.ReadString());
        }
    }

    // Fast Binary Encoding base JSON converter
    public static class Json
    {
        static Json()
        {
            CompositeResolver.RegisterAndSetAsDefault(new IJsonFormatter[]
            {
                new ByteArrayFormatter(),
                new BytesFormatter(),
                new CharFormatter(),
                new CharNullableFormatter(),
                new DateTimeFormatter(),
                new DateTimeNullableFormatter(),
                new DecimalFormatter(),
                new GuidFormatter()
            }, new[] { StandardResolver.Default });
        }

        public static string ToJson<T>(T value) { return Encoding.UTF8.GetString(JsonSerializer.Serialize(value)); }
        public static T FromJson<T>(string json) { return JsonSerializer.Deserialize<T>(json); }
    }
#elif NEWTONSOFTJSON
    public class BytesConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(MemoryStream));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(System.Convert.ToBase64String(((MemoryStream)value).GetBuffer()));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            var buffer = System.Convert.FromBase64String((string)reader.Value);
            return new MemoryStream(buffer, 0, buffer.Length, true, true);
        }
    }

    public class CharConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(Char)) || (objectType == typeof(Char?));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue((long)(char)value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            return (char)(long)reader.Value;
        }
    }

    public class DateTimeConverter : JsonConverter
    {
        private const long UnixEpoch = 621355968000000000;

        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(DateTime)) || (objectType == typeof(DateTime?));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            ulong nanoseconds = (ulong)((((DateTime)value).Ticks - UnixEpoch) * 100);
            writer.WriteValue(nanoseconds);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            ulong ticks = (ulong)(long)reader.Value / 100;
            return new DateTime((long)(UnixEpoch + ticks), DateTimeKind.Utc);
        }
    }

    public class DecimalConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(Decimal)) || (objectType == typeof(Decimal?));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((Decimal)value).ToString(CultureInfo.InvariantCulture));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            return Decimal.Parse((string)reader.Value, CultureInfo.InvariantCulture);
        }
    }

    public class GuidConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(Guid)) || (objectType == typeof(Guid?));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((Guid)value).ToString());
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            return Guid.Parse((string)reader.Value);
        }
    }

    // Fast Binary Encoding base JSON converter
    public static class Json
    {
        static Json()
        {
            JsonConvert.DefaultSettings = () => new JsonSerializerSettings
            {
                Converters = new List<JsonConverter>
                {
                    new BytesConverter(),
                    new CharConverter(),
                    new DateTimeConverter(),
                    new DecimalConverter(),
                    new GuidConverter()
                }
            };
        }

        public static string ToJson<T>(T value) { return JsonConvert.SerializeObject(value); }
        public static T FromJson<T>(string json) { return JsonConvert.DeserializeObject<T>(json); }
    }
#else
    public class ByteArrayConverter : JsonConverter<byte[]>
    {
        public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
        {
            writer.WriteStartArray();

            if (value.Length != 0)
                writer.WriteNumberValue(value[0]);
            for (int i = 1; i < value.Length; i++)
                writer.WriteNumberValue(value[i]);

            writer.WriteEndArray();
        }

        public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartArray)
                throw new JsonException("Expected start array token!");

            var array = new byte[4];
            var count = 0;
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndArray)
                    break;
                count++;
                if (array.Length < count)
                    Array.Resize(ref array, count * 2);
                array[count - 1] = reader.GetByte();
            }

            Array.Resize(ref array, count);
            return array;
        }
    }

    public class BytesConverter : JsonConverter<MemoryStream>
    {
        public override void Write(Utf8JsonWriter writer, MemoryStream value, JsonSerializerOptions options) => writer.WriteStringValue(System.Convert.ToBase64String(value.GetBuffer()));

        public override MemoryStream Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var buffer = System.Convert.FromBase64String(reader.GetString());
            return new MemoryStream(buffer, 0, buffer.Length, true, true);
        }
    }

    public class CharConverter : JsonConverter<Char>
    {
        public override void Write(Utf8JsonWriter writer, Char value, JsonSerializerOptions options) => writer.WriteNumberValue(value);
        public override Char Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => (Char)reader.GetInt64();
    }

    public class DateTimeConverter : JsonConverter<DateTime>
    {
        private const long UnixEpoch = 621355968000000000;

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            ulong nanoseconds = (ulong)((value.Ticks - UnixEpoch) * 100);
            writer.WriteNumberValue(nanoseconds);
        }

        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            ulong ticks = reader.GetUInt64() / 100;
            return new DateTime((long)(UnixEpoch + ticks), DateTimeKind.Utc);
        }
    }

    public class DecimalConverter : JsonConverter<Decimal>
    {
        public override void Write(Utf8JsonWriter writer, Decimal value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
        public override Decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Decimal.Parse(reader.GetString(), CultureInfo.InvariantCulture);
    }

    // Fast Binary Encoding base JSON converter
    public static class Json
    {
        private static readonly JsonSerializerOptions DefaultOptions;

        static Json()
        {
            DefaultOptions = new JsonSerializerOptions(JsonSerializerDefaults.General)
            {
                IncludeFields = true
            };
            DefaultOptions.Converters.Add(new ByteArrayConverter());
            DefaultOptions.Converters.Add(new BytesConverter());
            DefaultOptions.Converters.Add(new CharConverter());
            DefaultOptions.Converters.Add(new DateTimeConverter());
            DefaultOptions.Converters.Add(new DecimalConverter());
        }

        public static string ToJson<T>(T value) { return JsonSerializer.Serialize(value, DefaultOptions); }
        public static T FromJson<T>(string json) { return JsonSerializer.Deserialize<T>(json, DefaultOptions); }
    }
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCSharp::GenerateFBE(const std::string& domain, const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / (domain + "fbe.cs");
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");
    GenerateImports();

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + "FBE {");
    Indent(1);

    // Generate common models
    GenerateFBEUuidGenerator();
    GenerateFBEBuffer();
    GenerateFBEBaseModel();
    GenerateFBEFieldModelBase();
    GenerateFBEFieldModel("Bool", "bool", "", "1", "false");
    GenerateFBEFieldModel("Byte", "byte", "", "1", "(byte)0");
    GenerateFBEFieldModel("Char", "char", "(byte)", "1", "'\\0'");
    GenerateFBEFieldModel("WChar", "char", "(uint)", "4", "'\\0'");
    GenerateFBEFieldModel("Int8", "sbyte", "", "1", "0");
    GenerateFBEFieldModel("UInt8", "byte", "", "1", "(byte)0");
    GenerateFBEFieldModel("Int16", "short", "", "2", "0");
    GenerateFBEFieldModel("UInt16", "ushort", "", "2", "(ushort)0");
    GenerateFBEFieldModel("Int32", "int", "", "4", "0");
    GenerateFBEFieldModel("UInt32", "uint", "", "4", "0U");
    GenerateFBEFieldModel("Int64", "long", "", "8", "0L");
    GenerateFBEFieldModel("UInt64", "ulong", "", "8", "0UL");
    GenerateFBEFieldModel("Float", "float", "", "4", "0.0F");
    GenerateFBEFieldModel("Double", "double", "", "8", "0.0D");
    GenerateFBEFieldModel("Decimal", "decimal", "", "16", "0.0M");
    GenerateFBEFieldModel("UUID", "Guid", "", "16", "UuidGenerator.Nil()");
    GenerateFBEFieldModelTimestamp();
    GenerateFBEFieldModelBytes();
    GenerateFBEFieldModelString();
    GenerateFBEFieldModelOptional(true);
    GenerateFBEFieldModelOptional(false);
    GenerateFBEFieldModelArray(true, false);
    GenerateFBEFieldModelArray(false, false);
    GenerateFBEFieldModelArray(true, true);
    GenerateFBEFieldModelArray(false, true);
    GenerateFBEFieldModelVector(true, false);
    GenerateFBEFieldModelVector(false, false);
    GenerateFBEFieldModelVector(true, true);
    GenerateFBEFieldModelVector(false, true);
    GenerateFBEFieldModelMap(true, true, false);
    GenerateFBEFieldModelMap(true, false, false);
    GenerateFBEFieldModelMap(true, true, true);
    GenerateFBEFieldModelMap(true, false, true);
    GenerateFBEFieldModelMap(false, true, false);
    GenerateFBEFieldModelMap(false, false, false);
    GenerateFBEFieldModelMap(false, true, true);
    GenerateFBEFieldModelMap(false, false, true);
    if (Final())
    {
        GenerateFBEFinalModelBase();
        GenerateFBEFinalModel("Bool", "bool", "", "1", "false");
        GenerateFBEFinalModel("Byte", "byte", "", "1", "(byte)0");
        GenerateFBEFinalModel("Char", "char", "(byte)", "1", "'\\0'");
        GenerateFBEFinalModel("WChar", "char", "(uint)", "4", "'\\0'");
        GenerateFBEFinalModel("Int8", "sbyte", "", "1", "0");
        GenerateFBEFinalModel("UInt8", "byte", "", "1", "(byte)0");
        GenerateFBEFinalModel("Int16", "short", "", "2", "0");
        GenerateFBEFinalModel("UInt16", "ushort", "", "2", "(ushort)0");
        GenerateFBEFinalModel("Int32", "int", "", "4", "0");
        GenerateFBEFinalModel("UInt32", "uint", "", "4", "0U");
        GenerateFBEFinalModel("Int64", "long", "", "8", "0L");
        GenerateFBEFinalModel("UInt64", "ulong", "", "8", "0UL");
        GenerateFBEFinalModel("Float", "float", "", "4", "0.0F");
        GenerateFBEFinalModel("Double", "double", "", "8", "0.0D");
        GenerateFBEFinalModel("Decimal", "decimal", "", "16", "0.0M");
        GenerateFBEFinalModel("UUID", "Guid", "", "16", "UuidGenerator.Nil()");
        GenerateFBEFinalModelTimestamp();
        GenerateFBEFinalModelBytes();
        GenerateFBEFinalModelString();
        GenerateFBEFinalModelOptional(true);
        GenerateFBEFinalModelOptional(false);
        GenerateFBEFinalModelArray(true, false);
        GenerateFBEFinalModelArray(false, false);
        GenerateFBEFinalModelArray(true, true);
        GenerateFBEFinalModelArray(false, true);
        GenerateFBEFinalModelVector(true, false);
        GenerateFBEFinalModelVector(false, false);
        GenerateFBEFinalModelVector(true, true);
        GenerateFBEFinalModelVector(false, true);
        GenerateFBEFinalModelMap(true, true, false);
        GenerateFBEFinalModelMap(true, false, false);
        GenerateFBEFinalModelMap(true, true, true);
        GenerateFBEFinalModelMap(true, false, true);
        GenerateFBEFinalModelMap(false, true, false);
        GenerateFBEFinalModelMap(false, false, false);
        GenerateFBEFinalModelMap(false, true, true);
        GenerateFBEFinalModelMap(false, false, true);
    }
    if (Proto())
    {
        GenerateFBESender();
        GenerateFBEReceiver();
        GenerateFBEClient();
    }
    if (JSON())
        GenerateFBEJson();

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + "FBE");

    // Generate common footer
    GenerateFooter();

    // Store the common file
    WriteEnd();
    Store(common);
}

void GeneratorCSharp::GeneratePackage(const std::string& domain, const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate common file
    GenerateFBE(domain, output);

    // Generate the output file
    output /= domain + *p->name + ".cs";
    WriteBegin();

    // Generate package header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, p);

    // Generate namespace body
    if (p->body)
    {
        // Generate child enums
        for (const auto& child_e : p->body->enums)
            GenerateEnum(domain, p, child_e);

        // Generate child flags
        for (const auto& child_f : p->body->flags)
            GenerateFlags(domain, p, child_f);

        // Generate child structs
        for (const auto& child_s : p->body->structs)
            GenerateStruct(domain, p, child_s);
    }

    // Generate protocol
    if (Proto())
    {
        // Generate protocol version
        GenerateProtocolVersion(domain, p);

        // Generate sender & receiver
        GenerateSender(domain, p, false);
        GenerateReceiver(domain, p, false);
        GenerateProxy(domain, p, false);
        GenerateClient(domain, p, false);
        if (Final())
        {
            GenerateSender(domain, p, true);
            GenerateReceiver(domain, p, true);
            GenerateClient(domain, p, true);
        }
    }

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCSharp::GenerateEnum(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + " {");
    Indent(1);

    std::string json = R"CODE(
#if UTF8JSON
    public class _ENUM_NAME_Converter : IJsonFormatter<_ENUM_NAME_>
    {
        public void Serialize(ref JsonWriter writer, _ENUM_NAME_ value, IJsonFormatterResolver jsonFormatterResolver)
        {
            writer.Write_ENUM_UTF8JSON_TYPE_(value.Value);
        }

        public _ENUM_NAME_ Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            return new _ENUM_NAME_(reader.Read_ENUM_UTF8JSON_TYPE_());
        }
    }

    [JsonFormatter(typeof(_ENUM_NAME_Converter))]
#elif NEWTONSOFTJSON
    public class _ENUM_NAME_Converter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(_ENUM_NAME_);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((_ENUM_NAME_)value).Value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            switch (reader.Value)
            {
                case long longValue:
                    return new _ENUM_NAME_((_ENUM_TYPE_)longValue);
                case BigInteger bigValue:
                    return new _ENUM_NAME_((_ENUM_TYPE_)bigValue);
                default:
                    return null;
            }
        }
    }

    [JsonConverter(typeof(_ENUM_NAME_Converter))]
#else
    public class _ENUM_NAME_Converter : JsonConverter<_ENUM_NAME_>
    {
        public override void Write(Utf8JsonWriter writer, _ENUM_NAME_ value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value);
        public override _ENUM_NAME_ Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new _ENUM_NAME_(reader.Get_ENUM_UTF8JSON_TYPE_());
    }

    [JsonConverter(typeof(_ENUM_NAME_Converter))]
#endif)CODE";

    std::string code = R"CODE(
    public struct _ENUM_NAME_ : IComparable, IComparable<_ENUM_NAME_>, IEquatable<_ENUM_NAME_>
    {
        public _ENUM_TYPE_ Value { get; internal set; }

        public _ENUM_NAME_(_ENUM_TYPE_ value) : this()
        {
            Value = value;
        }

        public _ENUM_NAME_(_ENUM_NAME_ value) : this()
        {
            Value = value.Value;
        }

        public int CompareTo(object other)
        {
            return Value.CompareTo(((_ENUM_NAME_)other).Value);
        }

        public int CompareTo(_ENUM_NAME_ other)
        {
            return Value.CompareTo(other.Value);
        }

        public override bool Equals(object other)
        {
            return (other is _ENUM_NAME_ value) && Value.Equals(value.Value);
        }

        public bool Equals(_ENUM_NAME_ other)
        {
            return Value.Equals(other.Value);
        }

        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        public static bool operator==(_ENUM_NAME_ value1, _ENUM_NAME_ value2)
        {
            return value1.Value == value2.Value;
        }

        public static bool operator!=(_ENUM_NAME_ value1, _ENUM_NAME_ value2)
        {
            return value1.Value != value2.Value;
        }

        public static _ENUM_NAME_ Default => new _ENUM_NAME_();

        public static _DOMAIN_FBE.FieldModelValueType<_ENUM_NAME_> CreateFieldModel(_DOMAIN_FBE.Buffer buffer, long offset) { return new _PACKAGE_.FBE.FieldModel_ENUM_NAME_(buffer, offset); }

)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    // Prepare enum template
    json = std::regex_replace(json, std::regex("_ENUM_NAME_"), *e->name);
    json = std::regex_replace(json, std::regex("_ENUM_TYPE_"), enum_base_type);
    json = std::regex_replace(json, std::regex("_ENUM_UTF8JSON_TYPE_"), ConvertEnumTypeUtf8Json(enum_type));
    json = std::regex_replace(json, std::regex("\n"), EndLine());
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), *p->name);
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), enum_base_type);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate enum begin
    if (JSON())
        Write(json);
    Write(code);
    Indent(1);

    // Generate enum constants
    if (e->body)
    {
        int index = 0;
        std::string last = ConvertEnumConstant(enum_type, "0");
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
        {
            WriteIndent("public const " + enum_base_type + " _" + *(*it)->name + "_ = ");
            if ((*it)->value)
            {
                if ((*it)->value->constant && !(*it)->value->constant->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(enum_type, *(*it)->value->constant);
                    Write(last + " + " + std::to_string(index++));
                }
                else if ((*it)->value->reference && !(*it)->value->reference->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant("", *(*it)->value->reference);
                    Write("_" + last + "_");
                }
            }
            else
                Write(last + " + " + std::to_string(index++));
            WriteLine(";");
        }
        WriteLine();
    }

    // Generate enum body
    if (e->body)
    {
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
            WriteLineIndent("public static " + *e->name + " " + *(*it)->name + " = new " + *e->name + "(_" + *(*it)->name + "_);");
        WriteLine();
    }

    // Generate enum ToString() method
    WriteLineIndent("public override string ToString()");
    WriteLineIndent("{");
    Indent(1);
    if (e->body && !e->body->values.empty())
    {
        for (const auto& value : e->body->values)
            WriteLineIndent("if (this == " + *value->name + ")" + " return \"" + *value->name + "\";");
        WriteLineIndent("return \"<unknown>\";");
    }
    else
        WriteLineIndent("return \"<empty>\";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name);

    // Generate enum field model
    GenerateEnumFieldModel(domain, p, e);

    // Generate enum final model
    if (Final())
        GenerateEnumFinalModel(domain, p, e);
}

void GeneratorCSharp::GenerateEnumFieldModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string code = R"CODE(
    using global::_DOMAIN__PACKAGE_;

    // Fast Binary Encoding _ENUM_NAME_ field model
    public class FieldModel_ENUM_NAME_ : _DOMAIN_FBE.FieldModelValueType<_ENUM_NAME_>
    {
        public FieldModel_ENUM_NAME_(_DOMAIN_FBE.Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the field size
        public override long FBESize => _ENUM_SIZE_;

        // Clone the field model
        public override _DOMAIN_FBE.FieldModelValueType<_ENUM_NAME_> Clone() { return new FieldModel_ENUM_NAME_(_buffer, _offset); }

        // Get the value
        public override void Get(out _ENUM_NAME_ value) { Get(out value, _ENUM_NAME_.Default); }
        public override void Get(out _ENUM_NAME_ value, _ENUM_NAME_ defaults)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = defaults;
                return;
            }

            value = new _ENUM_NAME_((_ENUM_TYPE_)_ENUM_READ_(FBEOffset));
        }

        // Set the value
        public override void Set(_ENUM_NAME_ value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            Write(FBEOffset, value.Value);
        }
    }
)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    // Prepare enum model template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), *p->name);
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), enum_base_type);
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_READ_"), ConvertEnumRead(enum_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate enum model
    Write(code);

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateEnumFinalModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string code = R"CODE(
    using global::_DOMAIN__PACKAGE_;

    // Fast Binary Encoding _ENUM_NAME_ final model
    public class FinalModel_ENUM_NAME_ : _DOMAIN_FBE.FinalModelValueType<_ENUM_NAME_>
    {
        public FinalModel_ENUM_NAME_(_DOMAIN_FBE.Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public override long FBEAllocationSize(_ENUM_NAME_ value) { return FBESize; }

        // Get the final size
        public override long FBESize => _ENUM_SIZE_;

        // Clone the final model
        public override _DOMAIN_FBE.FinalModelValueType<_ENUM_NAME_> Clone() { return new FinalModel_ENUM_NAME_(_buffer, _offset); }

        // Check if the value is valid
        public override long Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return long.MaxValue;

            return FBESize;
        }

        // Get the value
        public override long Get(out _ENUM_NAME_ value)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = _ENUM_NAME_.Default;
                return 0;
            }

            value = new _ENUM_NAME_((_ENUM_TYPE_)_ENUM_READ_(FBEOffset));
            return FBESize;
        }

        // Set the value
        public override long Set(_ENUM_NAME_ value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return 0;

            Write(FBEOffset, value.Value);
            return FBESize;
        }
    }
)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    // Prepare enum model template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), *p->name);
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), enum_base_type);
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_READ_"), ConvertEnumRead(enum_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate enum model
    Write(code);

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateFlags(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + " {");
    Indent(1);

    std::string json = R"CODE(
#if UTF8JSON
    public class _FLAGS_NAME_Converter : IJsonFormatter<_FLAGS_NAME_>
    {
        public void Serialize(ref JsonWriter writer, _FLAGS_NAME_ value, IJsonFormatterResolver jsonFormatterResolver)
        {
            writer.Write_FLAGS_UTF8JSON_TYPE_(value.Value);
        }

        public _FLAGS_NAME_ Deserialize(ref JsonReader reader, IJsonFormatterResolver jsonFormatterResolver)
        {
            return new _FLAGS_NAME_(reader.Read_FLAGS_UTF8JSON_TYPE_());
        }
    }

    [JsonFormatter(typeof(_FLAGS_NAME_Converter))]
#elif NEWTONSOFTJSON
    public class _FLAGS_NAME_Converter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(_FLAGS_NAME_);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((_FLAGS_NAME_)value).Value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null)
                return null;

            switch (reader.Value)
            {
                case long longValue:
                    return new _FLAGS_NAME_((_FLAGS_TYPE_)longValue);
                case BigInteger bigValue:
                    return new _FLAGS_NAME_((_FLAGS_TYPE_)bigValue);
                default:
                    return null;
            }
        }
    }

    [JsonConverter(typeof(_FLAGS_NAME_Converter))]
#else
    public class _FLAGS_NAME_Converter : JsonConverter<_FLAGS_NAME_>
    {
        public override void Write(Utf8JsonWriter writer, _FLAGS_NAME_ value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value);
        public override _FLAGS_NAME_ Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new _FLAGS_NAME_(reader.Get_FLAGS_UTF8JSON_TYPE_());
    }

    [JsonConverter(typeof(_FLAGS_NAME_Converter))]
#endif)CODE";

    std::string code = R"CODE(
    public struct _FLAGS_NAME_ : IComparable, IComparable<_FLAGS_NAME_>, IEquatable<_FLAGS_NAME_>
    {
        public _FLAGS_TYPE_ Value { get; internal set; }

        public _FLAGS_NAME_(_FLAGS_TYPE_ value) : this()
        {
            Value = value;
        }

        public _FLAGS_NAME_(_FLAGS_NAME_ value) : this()
        {
            Value = value.Value;
        }

        public bool HasFlags(_FLAGS_NAME_ flags)
        {
            return (((Value & flags.Value) != 0) && ((Value & flags.Value) == flags.Value));
        }

        public _FLAGS_NAME_ SetFlags(_FLAGS_NAME_ flags)
        {
            Value |= flags.Value;
            return this;
        }

        public _FLAGS_NAME_ RemoveFlags(_FLAGS_NAME_ flags)
        {
            Value &= (byte)~flags.Value;
            return this;
        }

        public int CompareTo(object other)
        {
            return Value.CompareTo(((_FLAGS_NAME_)other).Value);
        }

        public int CompareTo(_FLAGS_NAME_ other)
        {
            return Value.CompareTo(other.Value);
        }

        public override bool Equals(object other)
        {
            return (other is _FLAGS_NAME_ value) && Value.Equals(value.Value);
        }

        public bool Equals(_FLAGS_NAME_ other)
        {
            return Value.Equals(other.Value);
        }

        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        public static bool operator==(_FLAGS_NAME_ flags1, _FLAGS_NAME_ flags2)
        {
            return flags1.Value == flags2.Value;
        }

        public static bool operator!=(_FLAGS_NAME_ flags1, _FLAGS_NAME_ flags2)
        {
            return flags1.Value != flags2.Value;
        }

        public static _FLAGS_NAME_ operator&(_FLAGS_NAME_ flags1, _FLAGS_NAME_ flags2)
        {
            return new _FLAGS_NAME_((byte)(flags1.Value & flags2.Value));
        }

        public static _FLAGS_NAME_ operator|(_FLAGS_NAME_ flags1, _FLAGS_NAME_ flags2)
        {
            return new _FLAGS_NAME_((byte)(flags1.Value | flags2.Value));
        }

        public static _FLAGS_NAME_ operator^(_FLAGS_NAME_ flags1, _FLAGS_NAME_ flags2)
        {
            return new _FLAGS_NAME_((byte)(flags1.Value ^ flags2.Value));
        }

        public static _FLAGS_NAME_ Default => new _FLAGS_NAME_();

        public static _DOMAIN_FBE.FieldModelValueType<_FLAGS_NAME_> CreateFieldModel(_DOMAIN_FBE.Buffer buffer, long offset) { return new _PACKAGE_.FBE.FieldModel_FLAGS_NAME_(buffer, offset); }

)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    // Prepare flags template
    json = std::regex_replace(json, std::regex("_FLAGS_NAME_"), *f->name);
    json = std::regex_replace(json, std::regex("_FLAGS_TYPE_"), flags_base_type);
    json = std::regex_replace(json, std::regex("_FLAGS_UTF8JSON_TYPE_"), ConvertEnumTypeUtf8Json(flags_type));
    json = std::regex_replace(json, std::regex("\n"), EndLine());
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), *p->name);
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), flags_base_type);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate flags begin
    if (JSON())
        Write(json);
    Write(code);
    Indent(1);

    // Generate flags constants
    if (f->body)
    {
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
        {
            if ((*it)->value)
            {
                if ((*it)->value->constant && !(*it)->value->constant->empty())
                    WriteLineIndent("public const " + flags_base_type + " _" + *(*it)->name + "_ = " + ConvertEnumConstant(flags_type, *(*it)->value->constant) + ";");
                else if ((*it)->value->reference && !(*it)->value->reference->empty())
                    WriteLineIndent("public const " + flags_base_type + " _" + *(*it)->name + "_ = _" + ConvertEnumConstant("", *(*it)->value->reference) + "_;");
            }
        }
        WriteLine();
    }

    // Generate flags body
    if (f->body)
    {
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
            if ((*it)->value)
                WriteLineIndent("public static " + *f->name + " " + *(*it)->name + " = new " + *f->name + "(_" + *(*it)->name + "_);");
        WriteLine();
    }

    // Generate flags ToString() method
    WriteLineIndent("public override string ToString()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var value = new " + *f->name + "(Value);");
    WriteLineIndent("var sb = new StringBuilder();");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("bool first = true;");
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if (HasFlags(" + *value->name + "))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("sb.Append(first ? \"\" : \"|\").Append(\"" + *value->name + "\");");
            WriteLineIndent("first = false;");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return sb.ToString();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name);

    // Generate flags field model
    GenerateFlagsFieldModel(domain, p, f);

    // Generate flags final model
    if (Final())
        GenerateFlagsFinalModel(domain, p, f);
}

void GeneratorCSharp::GenerateFlagsFieldModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string code = R"CODE(
    using global::_DOMAIN__PACKAGE_;

    // Fast Binary Encoding _FLAGS_NAME_ field model
    public class FieldModel_FLAGS_NAME_ : _DOMAIN_FBE.FieldModelValueType<_FLAGS_NAME_>
    {
        public FieldModel_FLAGS_NAME_(_DOMAIN_FBE.Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the field size
        public override long FBESize => _FLAGS_SIZE_;

        // Clone the field model
        public override _DOMAIN_FBE.FieldModelValueType<_FLAGS_NAME_> Clone() { return new FieldModel_FLAGS_NAME_(_buffer, _offset); }

        // Get the value
        public override void Get(out _FLAGS_NAME_ value) { Get(out value, _FLAGS_NAME_.Default); }
        public override void Get(out _FLAGS_NAME_ value, _FLAGS_NAME_ defaults)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = defaults;
                return;
            }

            value = new _FLAGS_NAME_((_FLAGS_TYPE_)_FLAGS_READ_(FBEOffset));
        }

        // Set the value
        public override void Set(_FLAGS_NAME_ value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return;

            Write(FBEOffset, value.Value);
        }
    }
)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    // Prepare flags model template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), *p->name);
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), flags_base_type);
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_READ_"), ConvertEnumRead(flags_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate flags model
    Write(code);

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateFlagsFinalModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string code = R"CODE(
    using global::_DOMAIN__PACKAGE_;

    // Fast Binary Encoding _FLAGS_NAME_ final model
    public class FinalModel_FLAGS_NAME_ : _DOMAIN_FBE.FinalModelValueType<_FLAGS_NAME_>
    {
        public FinalModel_FLAGS_NAME_(_DOMAIN_FBE.Buffer buffer, long offset) : base(buffer, offset) {}

        // Get the allocation size
        public override long FBEAllocationSize(_FLAGS_NAME_ value) { return FBESize; }

        // Get the final size
        public override long FBESize => _FLAGS_SIZE_;

        // Clone the final model
        public override _DOMAIN_FBE.FinalModelValueType<_FLAGS_NAME_> Clone() { return new FinalModel_FLAGS_NAME_(_buffer, _offset); }

        // Check if the value is valid
        public override long Verify()
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return long.MaxValue;

            return FBESize;
        }

        // Get the value
        public override long Get(out _FLAGS_NAME_ value)
        {
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
            {
                value = _FLAGS_NAME_.Default;
                return 0;
            }

            value = new _FLAGS_NAME_((_FLAGS_TYPE_)_FLAGS_READ_(FBEOffset));
            return FBESize;
        }

        // Set the value
        public override long Set(_FLAGS_NAME_ value)
        {
            Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), "Model is broken!");
            if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)
                return 0;

            Write(FBEOffset, value.Value);
            return FBESize;
        }
    }
)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    // Prepare flags model template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), *p->name);
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), flags_base_type);
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_READ_"), ConvertEnumRead(flags_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate flags model
    Write(code);

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateStruct(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + " {");
    Indent(1);

    // Generate struct begin
    WriteLine();
    WriteIndent("public struct " + *s->name + " : IComparable, IComparable<" + *s->name + ">, IEquatable<" + *s->name + ">");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate struct body
    bool id = false;
    if (s->base && !s->base->empty())
    {
        if (JSON())
        {
            WriteLineIndent("#if UTF8JSON");
            WriteLineIndent("[IgnoreDataMember]");
            WriteLineIndent("#else");
            WriteLineIndent("[JsonIgnore]");
            WriteLineIndent("#endif");
        }
        WriteLineIndent("public " + ConvertTypeName(*s->base, false) + " parent;");
        if (s->message)
        {
            id = true;
            WriteLineIndent("public Guid id => parent.id;");
        }
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->id)
                id = true;
            WriteLineIndent("public " + ConvertTypeName(*field) + " " + *field->name + ";");
        }
    }
    if (s->message && !id)
        WriteLineIndent("public Guid id => Guid.Empty;");

    // Generate struct FBE type property
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("public const long FBETypeConst = " + ConvertTypeName(*s->base, false) + ".FBETypeConst;");
    else
        WriteLineIndent("public const long FBETypeConst = " + std::to_string(s->type) + ";");
    if (JSON())
    {
        WriteLineIndent("#if UTF8JSON");
        WriteLineIndent("[IgnoreDataMember]");
        WriteLineIndent("#else");
        WriteLineIndent("[JsonIgnore]");
        WriteLineIndent("#endif");
    }
    WriteLineIndent("public long FBEType => FBETypeConst;");

    // Generate struct initialization property
    WriteLine();
    WriteLineIndent("public static " + *s->name + " Default => new " + *s->name);
    WriteLineIndent("{");
    Indent(1);
    bool first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent(std::string(first ? "" : ", ") + "parent = " + ConvertTypeName(*s->base, false) + ".Default");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent(std::string(first ? "" : ", ") + *field->name + " = " + ConvertDefault(domain, *p->name, *field));
            first = false;
        }
    }
    Indent(-1);
    WriteLineIndent("};");

    // Generate struct initialization constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        first = true;
        WriteLine();
        WriteIndent("public " + *s->name + "(");
        if (s->base && !s->base->empty())
        {
            Write(ConvertTypeName(*s->base, false) + " parent");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                Write(std::string(first ? "" : ", ") + ConvertTypeName(*field) + " " + *field->name);
                first = false;
            }
        }
        WriteLine(")");
        WriteLineIndent("{");
        Indent(1);
        if (s->base && !s->base->empty())
            WriteLineIndent("this.parent = parent;");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("this." + *field->name + " = " + *field->name + ";");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate struct Clone() method
    WriteLine();
    WriteLineIndent("public " + *s->name + " Clone()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Serialize the struct to the FBE stream");
    WriteLineIndent("var writer = new " + domain + *p->name + ".FBE." + *s->name + "Model();");
    WriteLineIndent("writer.Serialize(this);");
    WriteLine();
    WriteLineIndent("// Deserialize the struct from the FBE stream");
    WriteLineIndent("var reader = new " + domain + *p->name + ".FBE." + *s->name + "Model();");
    WriteLineIndent("reader.Attach(writer.Buffer);");
    WriteLineIndent("reader.Deserialize(out var result);");
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct CompareTo() methods
    WriteLine();
    WriteLineIndent("public int CompareTo(object other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("int result = 0;");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result = parent.CompareTo(other);");
        WriteLineIndent("if (result != 0)");
        Indent(1);
        WriteLineIndent("return result;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("result = " + *field->name + ".CompareTo(((" + *s->name + ")other)." + *field->name + ");");
                WriteLineIndent("if (result != 0)");
                Indent(1);
                WriteLineIndent("return result;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("public int CompareTo(" + *s->name + " other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("int result = 0;");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result = parent.CompareTo(other);");
        WriteLineIndent("if (result != 0)");
        Indent(1);
        WriteLineIndent("return result;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("result = " + *field->name + ".CompareTo(other." + *field->name + ");");
                WriteLineIndent("if (result != 0)");
                Indent(1);
                WriteLineIndent("return result;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct Equals() methods
    WriteLine();
    WriteLineIndent("public override bool Equals(object other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (!(other is " + *s->name + "))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if (!parent.Equals(other))");
        Indent(1);
        WriteLineIndent("return false;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("if (!" + *field->name + ".Equals(((" + *s->name + ")other)." + *field->name + "))");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("public bool Equals(" + *s->name + " other)");
    WriteLineIndent("{");
    Indent(1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if (!parent.Equals(other))");
        Indent(1);
        WriteLineIndent("return false;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("if (!" + *field->name + ".Equals(other." + *field->name + "))");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct GetHashCode() method
    WriteLine();
    WriteLineIndent("public override int GetHashCode()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("int hash = 17;");
    if (s->base && !s->base->empty())
        WriteLineIndent("hash = hash * 31 + parent.GetHashCode();");
    if (s->body)
        for (const auto& field : s->body->fields)
            if (field->keys)
                WriteLineIndent("hash = hash * 31 + " + *field->name + ".GetHashCode();");
    WriteLineIndent("return hash;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct operator== and operator!=
    WriteLine();
    WriteLineIndent("public static bool operator==(" + *s->name + " lhs, " + *s->name + " rhs) => lhs.Equals(rhs);");
    WriteLineIndent("public static bool operator!=(" + *s->name + " lhs, " + *s->name + " rhs) => !(lhs == rhs);");

    // Generate struct ToString() method
    WriteLine();
    WriteLineIndent("public override string ToString()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var sb = new StringBuilder();");
    WriteLineIndent("sb.Append(\"" + *s->name + "(\");");
    first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("sb.Append(parent.ToString());");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=***\");");
            else if (field->array)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").Append(" + *field->name + ".Length" + ").Append(\"][\");");
                WriteLineIndent("foreach (var item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.Append(\"]\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][]\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->vector)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").Append(" + *field->name + ".Count" + ").Append(\"][\");");
                WriteLineIndent("foreach (var item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.Append(\"]\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][]\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->list)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").Append(" + *field->name + ".Count" + ").Append(\"]<\");");
                WriteLineIndent("foreach (var item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.Append(\">\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]<>\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->set)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").Append(" + *field->name + ".Count" + ").Append(\"]{\");");
                WriteLineIndent("foreach (var item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.Append(\"}\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]{}\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->map)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").Append(" + *field->name + ".Count" + ").Append(\"]<{\");");
                WriteLineIndent("foreach (var item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.Key", false, true));
                WriteLineIndent("sb.Append(\"->\");");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.Value", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.Append(\"}>\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]<{}>\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->hash)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").Append(" + *field->name + ".Count" + ").Append(\"][{\");");
                WriteLineIndent("foreach (var item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.Key", false, true));
                WriteLineIndent("sb.Append(\"->\");");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.Value", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.Append(\"}]\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][{}]\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else
                WriteLineIndent("sb.Append(\"" + std::string(first ? "" : ",") + *field->name + "=\"); " + ConvertOutputStreamValue(*field->type, *field->name, field->optional, false));
            first = false;
        }
    }
    WriteLineIndent("sb.Append(\")\");");
    WriteLineIndent("return sb.ToString();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct JSON methods
    if (JSON())
    {
        WriteLine();
        WriteLineIndent("public string ToJson()");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("var json = " + domain + "FBE.Json.ToJson(this);");
        if (s->base && !s->base->empty())
        {
            WriteLineIndent("var jsonParent = parent.ToJson();");
            WriteLineIndent("json = json.Substring(0, json.Length - 1) + \",\" + jsonParent.Substring(1, jsonParent.Length - 2) + \"}\";");
        }
        WriteLineIndent("return json;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
        WriteLineIndent("public static " + *s->name + " FromJson(string json)");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("var result = " + domain + "FBE.Json.FromJson<" + *s->name + ">(json);");
        if (s->base && !s->base->empty())
            WriteLineIndent("result.parent = " + ConvertTypeName(*s->base, false) + ".FromJson(json);");
        WriteLineIndent("return result;");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate struct CreateFieldModel() method
    WriteLine();
    WriteLineIndent("public static " + domain + "FBE.FieldModelValueType<" + *s->name + "> CreateFieldModel(" + domain + "FBE.Buffer buffer, long offset) { return new " + *p->name + ".FBE.FieldModel" + *s->name + "(buffer, offset); }");

    // Generate struct end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name);

    // Generate struct field models
    GenerateStructFieldModel(domain, p, s);
    GenerateStructModel(domain, p, s);

    // Generate struct final models
    if (Final())
    {
        GenerateStructFinalModel(domain, p, s);
        GenerateStructModelFinal(domain, p, s);
    }
}

void GeneratorCSharp::GenerateStructFieldModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    // Generate using package
    WriteLine();
    WriteLineIndent("using global::" + domain + *p->name + ";");

    // Generate struct field model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " field model");
    WriteLineIndent("public class FieldModel" + *s->name + " : " + domain + "FBE.FieldModelValueType<" + *s->name + ">");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct field model accessors
    if (s->base && !s->base->empty())
        WriteLineIndent("public readonly " + ConvertTypeFieldName(domain, *p->name, *s->base, false) + " parent;");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("public readonly " + ConvertTypeFieldDeclaration(domain, *p->name, *field, false) + " " + *field->name + ";");

    // Generate struct field model constructor
    WriteLine();
    WriteLineIndent("public FieldModel" + *s->name + "(" + domain + "FBE.Buffer buffer, long offset) : base(buffer, offset)");
    WriteLineIndent("{");
    Indent(1);
    std::string prev_offset("4");
    std::string prev_size("4");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("parent = new " + ConvertTypeFieldName(domain, *p->name, *s->base, false) + "(buffer, " + prev_offset + " + " + prev_size + ");");
        prev_offset = "parent.FBEOffset";
        prev_size = "parent.FBEBody - 4 - 4";
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent(*field->name + " = " + ConvertTypeFieldInitialization(domain, *p->name, *field, prev_offset + " + " + prev_size, false) + ";");
            prev_offset = *field->name + ".FBEOffset";
            prev_size = *field->name + ".FBESize";
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model FBE properties
    WriteLine();
    WriteLineIndent("// Get the field size");
    WriteLineIndent("public override long FBESize => 4;");
    WriteLineIndent("// Get the field body size");
    WriteLineIndent("public long FBEBody");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("get");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeResult = 4 + 4");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.FBEBody - 4 - 4");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".FBESize");
    WriteLineIndent(";");
    Indent(-1);
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("// Get the field extra size");
    WriteLineIndent("public override long FBEExtra");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("get");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructOffset = ReadUInt32(FBEOffset);");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.Offset + fbeStructOffset + 4) > _buffer.Size))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.Shift(fbeStructOffset);");
    WriteLine();
    WriteLineIndent("long fbeResult = FBEBody");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.FBEExtra");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".FBEExtra");
    WriteLineIndent(";");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.Unshift(fbeStructOffset);");
    WriteLine();
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("// Get the field type");
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("public const long FBETypeConst = " + ConvertTypeFieldName(domain, *p->name, *s->base, false) + ".FBETypeConst;");
    else
        WriteLineIndent("public const long FBETypeConst = " + std::to_string(s->type) + ";");
    WriteLineIndent("public long FBEType => FBETypeConst;");

    // Generate struct field model Clone() method
    WriteLine();
    WriteLineIndent("// Clone the field model");
    WriteLineIndent("public override " + domain + "FBE.FieldModelValueType<" + *s->name + "> Clone() { return new FieldModel" + *s->name + "(_buffer, _offset); }");

    // Generate struct field model Verify() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("public override bool Verify() { return Verify(true); }");
    WriteLineIndent("public bool Verify(bool fbeVerifyType)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)");
    Indent(1);
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructOffset = ReadUInt32(FBEOffset);");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.Offset + fbeStructOffset + 4 + 4) > _buffer.Size))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructSize = ReadUInt32(fbeStructOffset);");
    WriteLineIndent("if (fbeStructSize < (4 + 4))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructType = ReadUInt32(fbeStructOffset + 4);");
    WriteLineIndent("if (fbeVerifyType && (fbeStructType != FBEType))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.Shift(fbeStructOffset);");
    WriteLineIndent("bool fbeResult = VerifyFields(fbeStructSize);");
    WriteLineIndent("_buffer.Unshift(fbeStructOffset);");
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model VerifyFields() method
    WriteLine();
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("public bool VerifyFields(long fbeStructSize)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentSize = 4 + 4;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + parent.FBEBody - 4 - 4) > fbeStructSize)");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
            WriteLineIndent("if (!parent.VerifyFields(fbeStructSize))");
            Indent(1);
            WriteLineIndent("return false;");
            Indent(-1);
            WriteLineIndent("fbeCurrentSize += parent.FBEBody - 4 - 4;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".FBESize) > fbeStructSize)");
                Indent(1);
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("if (!" + *field->name + ".Verify())");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
                WriteLineIndent("fbeCurrentSize += " + *field->name + ".FBESize;");
            }
        }
        WriteLine();
    }
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model GetBegin() method
    WriteLine();
    WriteLineIndent("// Get the struct value (begin phase)");
    WriteLineIndent("public long GetBegin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructOffset = ReadUInt32(FBEOffset);");
    WriteLineIndent("Debug.Assert(((fbeStructOffset > 0) && ((_buffer.Offset + fbeStructOffset + 4 + 4) <= _buffer.Size)), \"Model is broken!\");");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.Offset + fbeStructOffset + 4 + 4) > _buffer.Size))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructSize = ReadUInt32(fbeStructOffset);");
    WriteLineIndent("Debug.Assert((fbeStructSize >= (4 + 4)), \"Model is broken!\");");
    WriteLineIndent("if (fbeStructSize < (4 + 4))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.Shift(fbeStructOffset);");
    WriteLineIndent("return fbeStructOffset;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model GetEnd() method
    WriteLine();
    WriteLineIndent("// Get the struct value (end phase)");
    WriteLineIndent("public void GetEnd(long fbeBegin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.Unshift(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model Get() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("public override void Get(out " + *s->name + " fbeValue) { Get(out fbeValue, " + *s->name + ".Default); }");
    WriteLineIndent("public override void Get(out " + *s->name + " fbeValue, " + *s->name + " defaults)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = GetBegin();");
    WriteLineIndent("if (fbeBegin == 0)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("fbeValue = defaults;");
    WriteLineIndent("return;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("uint fbeStructSize = ReadUInt32(0);");
    WriteLineIndent("GetFields(out fbeValue, fbeStructSize);");
    WriteLineIndent("GetEnd(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model GetFields() method
    WriteLine();
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("public void GetFields(out " + *s->name + " fbeValue, long fbeStructSize)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentSize = 4 + 4;");
        WriteLine();
        WriteLineIndent("fbeValue = " + *s->name + ".Default;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + parent.FBEBody - 4 - 4) <= fbeStructSize)");
            Indent(1);
            WriteLineIndent("parent.GetFields(out fbeValue.parent, fbeStructSize);");
            Indent(-1);
            WriteLineIndent("fbeCurrentSize += parent.FBEBody - 4 - 4;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".FBESize) <= fbeStructSize)");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent(*field->name + ".Get(ref fbeValue." + *field->name + ");");
                else
                    WriteLineIndent(*field->name + ".Get(out fbeValue." + *field->name + (field->value ? (", " + ConvertConstant(domain, *p->name, *field->type, *field->value, field->optional)) : "") + ");");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                if (field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbeValue." + *field->name + ".Clear();");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + ConvertDefault(domain, *p->name, *field) + ";");
                Indent(-1);
                WriteLineIndent("fbeCurrentSize += " + *field->name + ".FBESize;");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model SetBegin() method
    WriteLine();
    WriteLineIndent("// Set the struct value (begin phase)");
    WriteLineIndent("public long SetBegin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("Debug.Assert(((_buffer.Offset + FBEOffset + FBESize) <= _buffer.Size), \"Model is broken!\");");
    WriteLineIndent("if ((_buffer.Offset + FBEOffset + FBESize) > _buffer.Size)");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeStructSize = (uint)FBEBody;");
    WriteLineIndent("uint fbeStructOffset = (uint)(_buffer.Allocate(fbeStructSize) - _buffer.Offset);");
    WriteLineIndent("Debug.Assert(((fbeStructOffset > 0) && ((_buffer.Offset + fbeStructOffset + fbeStructSize) <= _buffer.Size)), \"Model is broken!\");");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.Offset + fbeStructOffset + fbeStructSize) > _buffer.Size))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("Write(FBEOffset, fbeStructOffset);");
    WriteLineIndent("Write(fbeStructOffset, fbeStructSize);");
    WriteLineIndent("Write(fbeStructOffset + 4, (uint)FBEType);");
    WriteLine();
    WriteLineIndent("_buffer.Shift(fbeStructOffset);");
    WriteLineIndent("return fbeStructOffset;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model SetEnd() method
    WriteLine();
    WriteLineIndent("// Set the struct value (end phase)");
    WriteLineIndent("public void SetEnd(long fbeBegin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.Unshift(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model Set() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("public override void Set(" + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = SetBegin();");
    WriteLineIndent("if (fbeBegin == 0)");
    Indent(1);
    WriteLineIndent("return;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("SetFields(fbeValue);");
    WriteLineIndent("SetEnd(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model SetFields() method
    WriteLine();
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("public void SetFields(" + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("parent.SetFields(fbeValue.parent);");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent(*field->name + ".Set(fbeValue." + *field->name + ");");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateStructModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    // Generate using package
    WriteLine();
    WriteLineIndent("using global::" + domain + *p->name + ";");

    // Generate struct model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " model");
    WriteLineIndent("public class " + *s->name + "Model : " + domain + "FBE.Model");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct model accessor
    WriteLineIndent("public readonly FieldModel" + *s->name + " model;");

    // Generate struct model constructors
    WriteLine();
    WriteLineIndent("public " + *s->name + "Model() { model = new FieldModel" + *s->name + "(Buffer, 4); }");
    WriteLineIndent("public " + *s->name + "Model(" + domain + "FBE.Buffer buffer) : base(buffer) { model = new FieldModel" + *s->name + "(Buffer, 4); }");

    // Generate struct model FBE properties
    WriteLine();
    WriteLineIndent("// Get the model size");
    WriteLineIndent("public long FBESize => model.FBESize + model.FBEExtra;");
    WriteLineIndent("// Get the model type");
    WriteLineIndent("public const long FBETypeConst = FieldModel" + *s->name + ".FBETypeConst;");
    WriteLineIndent("public long FBEType => FBETypeConst;");

    // Generate struct model Verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("public bool Verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((Buffer.Offset + model.FBEOffset - 4) > Buffer.Size)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint fbeFullSize = ReadUInt32(model.FBEOffset - 4);");
    WriteLineIndent("if (fbeFullSize < model.FBESize)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return model.Verify();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model CreateBegin() method
    WriteLine();
    WriteLineIndent("// Create a new model (begin phase)");
    WriteLineIndent("public long CreateBegin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = Buffer.Allocate(4 + model.FBESize);");
    WriteLineIndent("return fbeBegin;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model CreateEnd() method
    WriteLine();
    WriteLineIndent("// Create a new model (end phase)");
    WriteLineIndent("public long CreateEnd(long fbeBegin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeEnd = Buffer.Size;");
    WriteLineIndent("uint fbeFullSize = (uint)(fbeEnd - fbeBegin);");
    WriteLineIndent("Write(model.FBEOffset - 4, fbeFullSize);");
    WriteLineIndent("return fbeFullSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model Serialize() method
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("public long Serialize(" + *s->name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = CreateBegin();");
    WriteLineIndent("model.Set(value);");
    WriteLineIndent("long fbeFullSize = CreateEnd(fbeBegin);");
    WriteLineIndent("return fbeFullSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model Deserialize() method
    WriteLine();
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("public long Deserialize(out " + *s->name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((Buffer.Offset + model.FBEOffset - 4) > Buffer.Size)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("value = " + *s->name + ".Default;");
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("uint fbeFullSize = ReadUInt32(model.FBEOffset - 4);");
    WriteLineIndent("Debug.Assert((fbeFullSize >= model.FBESize), \"Model is broken!\");");
    WriteLineIndent("if (fbeFullSize < model.FBESize)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("value = " + *s->name + ".Default;");
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("model.Get(out value);");
    WriteLineIndent("return fbeFullSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model Next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("public void Next(long prev)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("model.FBEShift(prev);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateStructFinalModel(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    // Generate using package
    WriteLine();
    WriteLineIndent("using global::" + domain + *p->name + ";");

    // Generate struct final model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("public class FinalModel" + *s->name + " : " + domain + "FBE.FinalModelValueType<" + *s->name + ">");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct final model accessors
    if (s->base && !s->base->empty())
        WriteLineIndent("public readonly " + ConvertTypeFieldName(domain, *p->name, *s->base, true) + " parent;");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("public readonly " + ConvertTypeFieldDeclaration(domain, *p->name, *field, true) + " " + *field->name + ";");

    // Generate struct final model constructor
    WriteLine();
    WriteLineIndent("public FinalModel" + *s->name + "(" + domain + "FBE.Buffer buffer, long offset) : base(buffer, offset)");
    WriteLineIndent("{");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("parent = new " + ConvertTypeFieldName(domain, *p->name, *s->base, true) + "(buffer, 0);");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent(*field->name + " = " + ConvertTypeFieldInitialization(domain, *p->name, *field, "0", true) + ";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model FBE properties
    WriteLine();
    WriteLineIndent("// Get the allocation size");
    WriteLineIndent("public override long FBEAllocationSize(" + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeResult = 0");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.FBEAllocationSize(fbeValue.parent)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".FBEAllocationSize(fbeValue." + *field->name + ")");
    WriteLineIndent(";");
    Indent(-1);
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("// Get the final type");
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("public const long FBETypeConst = " + ConvertTypeFieldName(domain, *p->name, *s->base, true) + ".FBETypeConst;");
    else
        WriteLineIndent("public const long FBETypeConst = " + std::to_string(s->type) + ";");
    WriteLineIndent("public long FBEType => FBETypeConst;");

    // Generate struct final model Clone() method
    WriteLine();
    WriteLineIndent("// Clone the final model");
    WriteLineIndent("public override " + domain + "FBE.FinalModelValueType<" + *s->name + "> Clone() { return new FinalModel" + *s->name + "(_buffer, _offset); }");

    // Generate struct final model Verify() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("public override long Verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.Shift(FBEOffset);");
    WriteLineIndent("long fbeResult = VerifyFields();");
    WriteLineIndent("_buffer.Unshift(FBEOffset);");
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model VerifyFields() method
    WriteLine();
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("public long VerifyFields()");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentOffset = 0;");
        WriteLineIndent("long fbeFieldSize;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.FBEOffset = fbeCurrentOffset;");
            WriteLineIndent("fbeFieldSize = parent.VerifyFields();");
            WriteLineIndent("if (fbeFieldSize == long.MaxValue)");
            Indent(1);
            WriteLineIndent("return long.MaxValue;");
            Indent(-1);
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".FBEOffset = fbeCurrentOffset;");
                WriteLineIndent("fbeFieldSize = " + *field->name + ".Verify();");
                WriteLineIndent("if (fbeFieldSize == long.MaxValue)");
                Indent(1);
                WriteLineIndent("return long.MaxValue;");
                Indent(-1);
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentOffset;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model Get() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("public override long Get(out " + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.Shift(FBEOffset);");
    WriteLineIndent("long fbeSize = GetFields(out fbeValue);");
    WriteLineIndent("_buffer.Unshift(FBEOffset);");
    WriteLineIndent("return fbeSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model GetFields() method
    WriteLine();
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("public long GetFields(out " + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentOffset = 0;");
        WriteLineIndent("long fbeCurrentSize = 0;");
        WriteLineIndent("long fbeFieldSize;");
        WriteLine();
        WriteLineIndent("fbeValue = " + *s->name + ".Default;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.FBEOffset = fbeCurrentOffset;");
            WriteLineIndent("fbeFieldSize = parent.GetFields(out fbeValue.parent);");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".FBEOffset = fbeCurrentOffset;");
                WriteLineIndent("fbeFieldSize = " + *field->name + ".Get(out fbeValue." + *field->name + ");");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model Set() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("public override long Set(" + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.Shift(FBEOffset);");
    WriteLineIndent("long fbeResult = SetFields(fbeValue);");
    WriteLineIndent("_buffer.Unshift(FBEOffset);");
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model SetFields() method
    WriteLine();
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("public long SetFields(" + *s->name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentOffset = 0;");
        WriteLineIndent("long fbeCurrentSize = 0;");
        WriteLineIndent("long fbeFieldSize;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.FBEOffset = fbeCurrentOffset;");
            WriteLineIndent("fbeFieldSize = parent.SetFields(fbeValue.parent);");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".FBEOffset = fbeCurrentOffset;");
                WriteLineIndent("fbeFieldSize = " + *field->name + ".Set(fbeValue." + *field->name + ");");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateStructModelFinal(const std::string& domain, const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    // Generate using package
    WriteLine();
    WriteLineIndent("using global::" + domain + *p->name + ";");

    // Generate struct model final begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("public class " + *s->name + "FinalModel : " + domain + "FBE.Model");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct model final accessor
    WriteLineIndent("private readonly FinalModel" + *s->name + " _model;");

    // Generate struct model final constructors
    WriteLine();
    WriteLineIndent("public " + *s->name + "FinalModel() { _model = new FinalModel" + *s->name + "(Buffer, 8); }");
    WriteLineIndent("public " + *s->name + "FinalModel(" + domain + "FBE.Buffer buffer) : base(buffer) { _model = new FinalModel" + *s->name + "(Buffer, 8); }");

    // Generate struct model final FBE properties
    WriteLine();
    WriteLineIndent("// Get the model type");
    WriteLineIndent("public const long FBETypeConst = FinalModel" + *s->name + ".FBETypeConst;");
    WriteLineIndent("public long FBEType => FBETypeConst;");

    // Generate struct model final Verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("public bool Verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((Buffer.Offset + _model.FBEOffset) > Buffer.Size)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("long fbeStructSize = ReadUInt32(_model.FBEOffset - 8);");
    WriteLineIndent("long fbeStructType = ReadUInt32(_model.FBEOffset - 4);");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != FBEType))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return ((8 + _model.Verify()) == fbeStructSize);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final Serialize() method
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("public long Serialize(" + *s->name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeInitialSize = Buffer.Size;");
    WriteLine();
    WriteLineIndent("uint fbeStructType = (uint)FBEType;");
    WriteLineIndent("uint fbeStructSize = (uint)(8 + _model.FBEAllocationSize(value));");
    WriteLineIndent("uint fbeStructOffset = (uint)(Buffer.Allocate(fbeStructSize) - Buffer.Offset);");
    WriteLineIndent("Debug.Assert(((Buffer.Offset + fbeStructOffset + fbeStructSize) <= Buffer.Size), \"Model is broken!\");");
    WriteLineIndent("if ((Buffer.Offset + fbeStructOffset + fbeStructSize) > Buffer.Size)");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbeStructSize = (uint)(8 + _model.Set(value));");
    WriteLineIndent("Buffer.Resize(fbeInitialSize + fbeStructSize);");
    WriteLine();
    WriteLineIndent("Write(_model.FBEOffset - 8, fbeStructSize);");
    WriteLineIndent("Write(_model.FBEOffset - 4, fbeStructType);");
    WriteLine();
    WriteLineIndent("return fbeStructSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final Deserialize() method
    WriteLine();
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("public long Deserialize(out " + *s->name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("Debug.Assert(((Buffer.Offset + _model.FBEOffset) <= Buffer.Size), \"Model is broken!\");");
    WriteLineIndent("if ((Buffer.Offset + _model.FBEOffset) > Buffer.Size)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("value = " + *s->name + ".Default;");
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("long fbeStructSize = ReadUInt32(_model.FBEOffset - 8);");
    WriteLineIndent("long fbeStructType = ReadUInt32(_model.FBEOffset - 4);");
    WriteLineIndent("Debug.Assert(((fbeStructSize > 0) && (fbeStructType == FBEType)), \"Model is broken!\");");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != FBEType))");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("value = " + *s->name + ".Default;");
    WriteLineIndent("return 8;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("return 8 + _model.Get(out value);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final Next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("public void Next(long prev)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_model.FBEShift(prev);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateProtocolVersion(const std::string& domain, const std::shared_ptr<Package>& p)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    // Generate protocol version class
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " protocol version");
    WriteLineIndent("public static class ProtocolVersion");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Protocol major version");
    WriteLineIndent("public const int Major = " + std::to_string(p->version->major) + ";");
    WriteLineIndent("// Protocol minor version");
    WriteLineIndent("public const int Minor = " + std::to_string(p->version->minor) + ";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateSender(const std::string& domain, const std::shared_ptr<Package>& p, bool final)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");
    std::string listener = "I" + sender + "Listener";

    // Generate sender listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final sender listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " sender listener interface");
    WriteIndent("public interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write(std::string(first ? "" : ", ") + domain + *import + ".FBE." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "FBE.ISenderListener");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate sender listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate sender begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final sender");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " sender");
    WriteLineIndent("public class " + sender + " : " + domain + "FBE.Sender, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLineIndent("// Imported senders");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public readonly " + *import + ".FBE." + sender + " " + *import + "Sender;");
        WriteLine();
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLineIndent("// Sender models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("public readonly " + *s->name + model + " " + *s->name + "Model;");
        WriteLine();
    }

    // Generate sender constructors
    WriteLineIndent("public " + sender + "() : base(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Sender = new " + *import + ".FBE." + sender + "(Buffer);");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "(Buffer);");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + sender + "(" + domain + "FBE.Buffer buffer) : base(buffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Sender = new " + *import + ".FBE." + sender + "(Buffer);");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "(Buffer);");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate send method
    WriteLineIndent("public long Send(object obj) { return SendListener(this, obj); }");
    WriteLineIndent("public long SendListener(" + listener + " listener, object obj)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (obj)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "global::" + domain + *p->name + "." + *s->name;
                WriteLineIndent("case " + struct_name + " value when value.FBEType == " + struct_name + ".FBETypeConst: return SendListener(listener, value);");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (p->import)
    {
        WriteLineIndent("long result;");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = " + *import + "Sender.SendListener(listener, obj);");
            WriteLineIndent("if (result > 0)");
            Indent(1);
            WriteLineIndent("return result;");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate send methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "global::" + domain + *p->name + "." + *s->name;
                WriteLineIndent("public long Send(" + struct_name + " value) { return SendListener(this, value); }");
                WriteLineIndent("public long SendListener(" + listener + " listener, " + struct_name + " value)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("long serialized = " + *s->name + "Model.Serialize(value);");
                WriteLineIndent("Debug.Assert((serialized > 0), \"" + domain + *p->name + "." + *s->name + " serialization failed!\");");
                WriteLineIndent("Debug.Assert(" + *s->name + "Model.Verify(), \"" + domain + *p->name + "." + *s->name + " validation failed!\");");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (Logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("string message = value.ToString();");
                WriteLineIndent("listener.OnSendLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return SendSerialized(listener, serialized);");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate sender end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateReceiver(const std::string& domain, const std::shared_ptr<Package>& p, bool final)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");
    std::string listener = "I" + receiver + "Listener";

    // Generate receiver listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final receiver listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " receiver listener interface");
    WriteIndent("public interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write(std::string(first ? "" : ", ") + domain + *import + ".FBE." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "FBE.IReceiverListener");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate receiver handlers
    if (p->body)
    {
        WriteLineIndent("// Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent("void OnReceive(" + struct_name + " value) {}");
            }
        }
    }

    // Generate receiver listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final receiver");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " receiver");
    WriteLineIndent("public class " + receiver + " : " + domain + "FBE.Receiver, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported receivers accessors
    if (p->import)
    {
        WriteLineIndent("// Imported receivers");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public " + *import + ".FBE." + receiver + " " + *import + "Receiver;");
        WriteLine();
    }

    // Generate receiver models accessors
    if (p->body)
    {
        WriteLineIndent("// Receiver values accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent("private " + struct_name + " " + *s->name + "Value;");
            }
        }
        WriteLine();
        WriteLineIndent("// Receiver models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private readonly " + *s->name + model + " " + *s->name + "Model;");
        WriteLine();
    }

    // Generate receiver constructors
    WriteLineIndent("public " + receiver + "() : base(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Receiver = new " + *import + ".FBE." + receiver + "(Buffer);");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent(*s->name + "Value = " + struct_name + ".Default;");
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + receiver + "(" + domain + "FBE.Buffer buffer) : base(buffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Receiver = new " + *import + ".FBE." + receiver + "(Buffer);");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent(*s->name + "Value = " + struct_name + ".Default;");
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate receiver message handler
    WriteLineIndent("internal override bool OnReceive(long type, byte[] buffer, long offset, long size) { return OnReceiveListener(this, type, buffer, offset, size); }");
    WriteLineIndent("internal bool OnReceiveListener(" + listener + " listener, long type, byte[] buffer, long offset, long size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case " + *s->name + model + ".FBETypeConst:");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent(*s->name + "Model.Attach(buffer, offset);");
                WriteLineIndent("Debug.Assert(" + *s->name + "Model.Verify(), \"" + domain + *p->name + "." + *s->name + " validation failed!\");");
                WriteLineIndent("long deserialized = " + *s->name + "Model.Deserialize(out " + *s->name + "Value);");
                WriteLineIndent("Debug.Assert((deserialized > 0), \"" + domain + *p->name + "." + *s->name + " deserialization failed!\");");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (Logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("string message = " + *s->name + "Value.ToString();");
                WriteLineIndent("listener.OnReceiveLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("listener.OnReceive(" + *s->name + "Value);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (p->import)
    {
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "Receiver.OnReceiveListener(listener, type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateProxy(const std::string& domain, const std::shared_ptr<Package>& p, bool final)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");
    std::string listener = "I" + proxy + "Listener";

    // Generate proxy listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final proxy listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " proxy listener interface");
    WriteIndent("public interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write(std::string(first ? "" : ", ") + domain + *import + ".FBE." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "FBE.IReceiverListener");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate proxy handlers
    if (p->body)
    {
        WriteLineIndent("// Proxy handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_model = *s->name + model;
                WriteLineIndent("void OnProxy(" + struct_model + " model, long type, byte[] buffer, long offset, long size) {}");
            }
        }
    }

    // Generate proxy listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate proxy begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final proxy");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " proxy");
    WriteLineIndent("public class " + proxy + " : " + domain + "FBE.Receiver, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported proxy accessors
    if (p->import)
    {
        WriteLineIndent("// Imported proxy");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public " + *import + ".FBE." + proxy + " " + *import + "Proxy;");
        WriteLine();
    }

    // Generate proxy models accessors
    if (p->body)
    {
        WriteLineIndent("// Proxy models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private readonly " + *s->name + model + " " + *s->name + "Model;");
        WriteLine();
    }

    // Generate proxy constructors
    WriteLineIndent("public " + proxy + "() : base(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Proxy = new " + *import + ".FBE." + proxy + "(Buffer);");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + proxy + "(" + domain + "FBE.Buffer buffer) : base(buffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Proxy = new " + *import + ".FBE." + proxy + "(Buffer);");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate proxy message handler
    WriteLineIndent("internal override bool OnReceive(long type, byte[] buffer, long offset, long size) { return OnReceiveListener(this, type, buffer, offset, size); }");
    WriteLineIndent("internal bool OnReceiveListener(" + listener + " listener, long type, byte[] buffer, long offset, long size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case " + *s->name + model + ".FBETypeConst:");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Attach the FBE stream to the proxy model");
                WriteLineIndent(*s->name + "Model.Attach(buffer, offset);");
                WriteLineIndent("Debug.Assert(" + *s->name + "Model.Verify(), \"" + domain + *p->name + "." + *s->name + " validation failed!\");");
                WriteLine();
                WriteLineIndent("long fbeBegin = " + *s->name + "Model.model.GetBegin();");
                WriteLineIndent("if (fbeBegin == 0)");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
                WriteLineIndent("// Call proxy handler");
                WriteLineIndent("listener.OnProxy(" + *s->name + "Model, type, buffer, offset, size);");
                WriteLineIndent(*s->name + "Model.model.GetEnd(fbeBegin);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (p->import)
    {
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "Proxy.OnReceiveListener(listener, type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate proxy end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

void GeneratorCSharp::GenerateClient(const std::string& domain, const std::shared_ptr<Package>& p, bool final)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + domain + *p->name + ".FBE {");
    Indent(1);

    std::string sender = (final ? "FinalSender" : "Sender");
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string client = (final ? "FinalClient" : "Client");
    std::string model = (final ? "FinalModel" : "Model");
    std::string listener = "I" + client + "Listener";
    std::string sender_listener = "I" + sender + "Listener";
    std::string receiver_listener = "I" + receiver + "Listener";

    // Generate client listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final client listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " client listener interface");
    WriteIndent("public interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write(std::string(first ? "" : ", ") + domain + *import + ".FBE." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "FBE.IClientListener");
    Write(", " + sender_listener + ", " + receiver_listener);
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate client listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate client begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final client");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " client");
    WriteLineIndent("public class " + client + " : " + domain + "FBE.Client, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported clients accessors
    if (p->import)
    {
        WriteLineIndent("// Imported clients");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public readonly " + *import + ".FBE." + client + " " + *import + "Client;");
        WriteLine();
    }

    // Generate client sender models accessors
    if (p->body)
    {
        WriteLineIndent("// Client sender models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("public readonly " + *s->name + model + " " + *s->name + "SenderModel;");
        WriteLine();
    }

    // Generate client receiver models accessors
    if (p->body)
    {
        WriteLineIndent("// Client receiver values accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent("private " + struct_name + " " + *s->name + "ReceiverValue;");
            }
        }
        WriteLine();
        WriteLineIndent("// Client receiver models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private readonly " + *s->name + model + " " + *s->name + "ReceiverModel;");
        WriteLine();
    }

    // Collect responses & rejects collections
    std::set<std::string> responses;
    std::map<std::string, bool> rejects;
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message && s->request)
            {
                std::string response_type = (s->response) ? ConvertTypeName(*s->response->response, false) : "";

                if (!response_type.empty())
                {
                    // Update responses and rejects cache
                    responses.insert(*s->response->response);
                    if (s->rejects)
                        for (const auto& reject : s->rejects->rejects)
                            rejects[*reject.reject] = reject.global;
                }
            }
        }
    }

    // Generate client requests cache fields
    if (!responses.empty())
    {
        WriteLineIndent("// Client requests cache fields");
        for (const auto& response : responses)
        {
            std::string response_name = response;
            std::string response_type = ConvertTypeName(domain, *p->name, response, false);
            CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

            WriteLineIndent("private Dictionary<Guid, Tuple<DateTime, TimeSpan, TaskCompletionSource<" + response_type + ">>> _requestsById" + response_name + ";");
            WriteLineIndent("private SortedDictionary<DateTime, Guid> _requestsByTimestamp" + response_name + ";");
        }
        WriteLine();
    }

    // Generate client constructors
    WriteLineIndent("public " + client + "() : base(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Client = new " + *import + ".FBE." + client + "(SendBuffer, ReceiveBuffer);");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent(*s->name + "SenderModel = new " + *s->name + model + "(SendBuffer);");
                WriteLineIndent(*s->name + "ReceiverValue = " + struct_name + ".Default;");
                WriteLineIndent(*s->name + "ReceiverModel = new " + *s->name + model + "();");
            }
        }
    }
    if (!responses.empty())
    {
        for (const auto& response : responses)
        {
            std::string response_name = response;
            std::string response_type = ConvertTypeName(domain, *p->name, response, false);
            CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

            WriteLineIndent("_requestsById" + response_name + " = new Dictionary<Guid, Tuple<DateTime, TimeSpan, TaskCompletionSource<" + response_type + ">>>();");
            WriteLineIndent("_requestsByTimestamp" + response_name + " = new SortedDictionary<DateTime, Guid>();");
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + client + "(" + domain + "FBE.Buffer sendBuffer, " + domain + "FBE.Buffer receiveBuffer) : base(sendBuffer, receiveBuffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Client = new " + *import + ".FBE." + client + "(SendBuffer, ReceiveBuffer);");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent(*s->name + "SenderModel = new " + *s->name + model + "(SendBuffer);");
                WriteLineIndent(*s->name + "ReceiverValue = " + struct_name + ".Default;");
                WriteLineIndent(*s->name + "ReceiverModel = new " + *s->name + model + "();");
            }
        }
    }
    if (!responses.empty())
    {
        for (const auto& response : responses)
        {
            std::string response_name = response;
            std::string response_type = ConvertTypeName(domain, *p->name, response, false);
            CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

            WriteLineIndent("_requestsById" + response_name + " = new Dictionary<Guid, Tuple<DateTime, TimeSpan, TaskCompletionSource<" + response_type + ">>>();");
            WriteLineIndent("_requestsByTimestamp" + response_name + " = new SortedDictionary<DateTime, Guid>();");
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate request methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message && s->request)
            {
                std::string request_name = *s->name;
                std::string request_type = ConvertTypeName(domain, *p->name, *s->name, false);
                std::string response_type = (s->response) ? ConvertTypeName(domain, *p->name, *s->response->response, false) : "";
                std::string response_field = (s->response) ? *s->response->response : "";
                CppCommon::StringUtils::ReplaceAll(request_name, ".", "");
                CppCommon::StringUtils::ReplaceAll(response_field, ".", "");

                WriteLine();
                if (response_type.empty())
                {
                    WriteLineIndent("public Task Request(" + request_type + " value) { return RequestListener(this, value, TimeSpan.Zero); }");
                    WriteLineIndent("public Task Request(" + request_type + " value, TimeSpan timeout) { return RequestListener(this, value, timeout); }");
                    WriteLineIndent("public Task RequestListener(" + listener + " listener, " + request_type + " value) { return RequestListener(listener, value, TimeSpan.Zero); }");
                    WriteLineIndent("public Task RequestListener(" + listener + " listener, " + request_type + " value, TimeSpan timeout)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("var source = new TaskCompletionSource();");
                    WriteLineIndent("Task task = source.Task;");
                    WriteLine();
                    WriteLineIndent("// Send the request message");
                    WriteLineIndent("long serialized = SendListener(listener, value);");
                    WriteLineIndent("if (serialized > 0)");
                    Indent(1);
                    WriteLineIndent("source.SetResult();");
                    Indent(-1);
                    WriteLineIndent("else");
                    Indent(1);
                    WriteLineIndent("source.SetException(new Exception(\"Send request failed!\"));");
                    Indent(-1);
                    WriteLine();
                    WriteLineIndent("return task;");
                    Indent(-1);
                    WriteLineIndent("}");
                }
                else
                {
                    WriteLineIndent("public Task<" + response_type + "> Request(" + request_type + " value) { return RequestListener(this, value, TimeSpan.Zero); }");
                    WriteLineIndent("public Task<" + response_type + "> Request(" + request_type + " value, TimeSpan timeout) { return RequestListener(this, value, timeout); }");
                    WriteLineIndent("public Task<" + response_type + "> RequestListener(" + listener + " listener, " + request_type + " value) { return RequestListener(listener, value, TimeSpan.Zero); }");
                    WriteLineIndent("public Task<" + response_type + "> RequestListener(" + listener + " listener, " + request_type + " value, TimeSpan timeout)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("lock (Lock)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("var source = new TaskCompletionSource<" + response_type + ">();");
                    WriteLineIndent("Task<" + response_type + "> task = source.Task;");
                    WriteLine();
                    WriteLineIndent("DateTime current = DateTime.UtcNow;");
                    WriteLine();
                    WriteLineIndent("// Send the request message");
                    WriteLineIndent("long serialized = SendListener(listener, value);");
                    WriteLineIndent("if (serialized > 0)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("// Calculate the unique timestamp");
                    WriteLineIndent("Timestamp = (current <= Timestamp) ? new DateTime(Timestamp.Ticks + 1) : current;");
                    WriteLine();
                    WriteLineIndent("// Register the request");
                    WriteLineIndent("_requestsById" + response_field + ".Add(value.id, new Tuple<DateTime, TimeSpan, TaskCompletionSource<" + response_type + ">>(Timestamp, timeout, source));");
                    WriteLineIndent("if (timeout.Ticks > 0)");
                    Indent(1);
                    WriteLineIndent("_requestsByTimestamp" + response_field + ".Add(Timestamp, value.id);");
                    Indent(-1);
                    Indent(-1);
                    WriteLineIndent("}");
                    WriteLineIndent("else");
                    Indent(1);
                    WriteLineIndent("source.SetException(new Exception(\"Send request failed!\"));");
                    Indent(-1);
                    WriteLine();
                    WriteLineIndent("return task;");
                    Indent(-1);
                    WriteLineIndent("}");
                    Indent(-1);
                    WriteLineIndent("}");
                }
            }
        }
    }

    // Generate client send method
    WriteLine();
    WriteLineIndent("public long Send(object obj) { return SendListener(this, obj); }");
    WriteLineIndent("public long SendListener(" + listener + " listener, object obj)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (obj)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent("case " + struct_name + " value when value.FBEType == " + struct_name + ".FBETypeConst: return SendListener(listener, value);");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (p->import)
    {
        WriteLineIndent("long result;");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = " + *import + "Client.SendListener(listener, obj);");
            WriteLineIndent("if (result > 0)");
            Indent(1);
            WriteLineIndent("return result;");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate client send methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = ConvertTypeName(domain, *p->name, *s->name, false);
                WriteLineIndent("public long Send(" + struct_name + " value) { return SendListener(this, value); }");
                WriteLineIndent("public long SendListener(" + listener + " listener, " + struct_name + " value)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("long serialized = " + *s->name + "SenderModel.Serialize(value);");
                WriteLineIndent("Debug.Assert((serialized > 0), \"" + domain + *p->name + "." + *s->name + " serialization failed!\");");
                WriteLineIndent("Debug.Assert(" + *s->name + "SenderModel.Verify(), \"" + domain + *p->name + "." + *s->name + " validation failed!\");");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (Logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("string message = value.ToString();");
                WriteLineIndent("listener.OnSendLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return SendSerialized(listener, serialized);");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate response handlers
    for (const auto& response : responses)
    {
        std::string response_name = response;
        std::string response_type = ConvertTypeName(domain, *p->name, response, false);
        CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

        WriteLine();
        WriteLineIndent("public bool OnReceiveResponse(" + response_type + " response)");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("ReceivedResponse_" + response_name + "?.Invoke(response);");
        WriteLine();
        if (p->body)
        {
            std::set<std::string> cache;
            WriteLineIndent("lock (Lock)");
            WriteLineIndent("{");
            Indent(1);
            for (const auto& s : p->body->structs)
            {
                if (s->message && s->response)
                {
                    std::string struct_response_name = *s->response->response;
                    std::string struct_response_type = ConvertTypeName(domain, *p->name, *s->response->response, false);
                    CppCommon::StringUtils::ReplaceAll(struct_response_name, ".", "");

                    if ((struct_response_type == response_type) && (cache.find(struct_response_type) == cache.end()))
                    {
                        WriteLineIndent("if (_requestsById" + struct_response_name + ".TryGetValue(response.id, out Tuple<DateTime, TimeSpan, TaskCompletionSource<" + struct_response_type + ">> tuple))");
                        WriteLineIndent("{");
                        Indent(1);
                        WriteLineIndent("var timestamp = tuple.Item1;");
                        WriteLineIndent("var timespan = tuple.Item2;");
                        WriteLineIndent("var source = tuple.Item3;");
                        WriteLineIndent("source.SetResult(response);");
                        WriteLineIndent("_requestsById" + struct_response_name + ".Remove(response.id);");
                        WriteLineIndent("_requestsByTimestamp" + struct_response_name + ".Remove(timestamp);");
                        WriteLineIndent("return true;");
                        cache.insert(struct_response_type);
                        Indent(-1);
                        WriteLineIndent("}");
                        WriteLine();
                    }
                }
            }
        }
        WriteLineIndent("return false;");
        Indent(-1);
        WriteLineIndent("}");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate remaining response handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_response_name = *s->name;
                std::string struct_response_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_response_name, ".", "");

                if ((responses.find(*s->name) == responses.end()) && (cache.find(struct_response_type) == cache.end()))
                {
                    if (!found)
                        WriteLine();
                    WriteLineIndent("public bool OnReceiveResponse(" + struct_response_type + " response) { ReceivedResponse_" + struct_response_name + "?.Invoke(response); return false; }");
                    cache.insert(struct_response_type);
                    found = true;
                }
            }
        }
    }

    // Generate reject handlers
    for (const auto& reject : rejects)
    {
        std::string reject_name = reject.first;
        std::string reject_type = ConvertTypeName(domain, *p->name, reject.first, false);
        bool global = reject.second;
        bool imported = CppCommon::StringUtils::ReplaceAll(reject_name, ".", "");
        CppCommon::StringUtils::ReplaceAll(reject_name, ".", "");

        WriteLine();
        WriteLineIndent("public bool OnReceiveReject(" + reject_type + " reject)");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("ReceivedReject_" + reject_name + "?.Invoke(reject);");
        WriteLine();
        if (global)
        {
            if (p->import)
            {
                for (const auto& import : p->import->imports)
                {
                    WriteLineIndent("if (" + *import + "Client.OnReceiveReject(reject))");
                    Indent(1);
                    WriteLineIndent("return true;");
                    Indent(-1);
                }
                WriteLine();
            }
        }
        else if (imported)
        {
            std::string ns = "";
            std::string t = reject.first;
            std::string type = reject.first;

            size_t pos = type.find_last_of('.');
            if (pos != std::string::npos)
            {
                ns.assign(type, 0, pos);
                t.assign(type, pos + 1, type.size() - pos);
            }

            WriteLineIndent("if (" + ns + "Client.OnReceiveReject(reject))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
            WriteLine();
        }
        if (p->body)
        {
            std::set<std::string> cache;
            WriteLineIndent("lock (Lock)");
            WriteLineIndent("{");
            Indent(1);
            WriteLine();
            for (const auto& s : p->body->structs)
            {
                if (s->message && s->response && s->rejects)
                {
                    for (const auto& r : s->rejects->rejects)
                    {
                        std::string struct_response_name = *s->response->response;
                        std::string struct_response_type = ConvertTypeName(domain, *p->name, *s->response->response, false);
                        CppCommon::StringUtils::ReplaceAll(struct_response_name, ".", "");

                        std::string struct_reject_name = *r.reject;
                        std::string struct_reject_type = ConvertTypeName(domain, *p->name, *r.reject, false);
                        CppCommon::StringUtils::ReplaceAll(struct_reject_name, ".", "");

                        if ((struct_reject_type == reject_type) && (cache.find(struct_response_name) == cache.end()))
                        {
                            WriteLineIndent("if (_requestsById" + struct_response_name + ".TryGetValue(reject.id, out Tuple<DateTime, TimeSpan, TaskCompletionSource<" + struct_response_type + ">> tuple" + struct_response_name + "))");
                            WriteLineIndent("{");
                            Indent(1);
                            WriteLineIndent("var timestamp = tuple" + struct_response_name + ".Item1;");
                            WriteLineIndent("var timespan = tuple" + struct_response_name + ".Item2;");
                            WriteLineIndent("var source = tuple" + struct_response_name + ".Item3;");
                            WriteLineIndent("source.SetException(new Exception(reject.ToString()));");
                            WriteLineIndent("_requestsById" + struct_response_name + ".Remove(reject.id);");
                            WriteLineIndent("_requestsByTimestamp" + struct_response_name + ".Remove(timestamp);");
                            WriteLineIndent("return true;");
                            cache.insert(struct_response_name);
                            Indent(-1);
                            WriteLineIndent("}");
                            WriteLine();
                        }
                    }
                }
            }
        }
        WriteLineIndent("return false;");
        Indent(-1);
        WriteLineIndent("}");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate remaining reject handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_reject_name = *s->name;
                std::string struct_reject_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_reject_name, ".", "");

                if ((rejects.find(*s->name) == rejects.end()) && (cache.find(struct_reject_type) == cache.end()))
                {
                    if (!found)
                        WriteLine();
                    WriteLineIndent("public bool OnReceiveReject(" + struct_reject_type + " reject) { ReceivedReject_" + struct_reject_name + "?.Invoke(reject); return false; }");
                    cache.insert(struct_reject_type);
                    found = true;
                }
            }
        }
    }

    // Generate notify handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_notify_name = *s->name;
                std::string struct_notify_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_notify_name, ".", "");

                if (cache.find(struct_notify_type) == cache.end())
                {
                    if (!found)
                        WriteLine();
                    WriteLineIndent("public void OnReceiveNotify(" + struct_notify_type + " notify) { ReceivedNotify_" + struct_notify_name + "?.Invoke(notify); }");
                    cache.insert(struct_notify_type);
                    found = true;
                }
            }
        }
    }

    // Generate receive handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_response_name = *s->name;
                std::string struct_response_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_response_name, ".", "");

                if (cache.find(struct_response_type) == cache.end())
                {
                    if (!found)
                        WriteLine();
                    WriteLineIndent("public void OnReceive(" + struct_response_type + " value) { if (!OnReceiveResponse(value) && !OnReceiveReject(value)) OnReceiveNotify(value); }");
                    cache.insert(struct_response_type);
                    found = true;
                }
            }
        }
    }

    // Generate reset requests method
    WriteLine();
    WriteLineIndent("// Reset client requests");
    WriteLineIndent("internal override void ResetRequests()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("base.ResetRequests();");
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Client.ResetRequests();");
    }
    for (const auto& response : responses)
    {
        std::string response_name = response;
        std::string response_type = ConvertTypeName(domain, *p->name, response, false);
        CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

        WriteLine();
        WriteLineIndent("foreach(var request in _requestsById" + response_name + ")");
        Indent(1);
        WriteLineIndent("request.Value.Item3.SetException(new Exception(\"Reset client!\"));");
        Indent(-1);
        WriteLineIndent("_requestsById" + response_name + ".Clear();");
        WriteLineIndent("_requestsByTimestamp" + response_name + ".Clear();");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate watchdog requests method
    WriteLine();
    WriteLineIndent("// Watchdog client requests for timeouts");
    WriteLineIndent("internal override void WatchdogRequests(DateTime utc)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("base.WatchdogRequests(utc);");
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Client.WatchdogRequests(utc);");
    }
    for (const auto& response : responses)
    {
        std::string response_name = response;
        std::string response_type = ConvertTypeName(domain, *p->name, response, false);
        CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

        WriteLine();
        WriteLineIndent("while (_requestsByTimestamp" + response_name + ".Count > 0)");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("var request = _requestsByTimestamp" + response_name + ".First();");
        WriteLineIndent("Tuple<DateTime, TimeSpan, TaskCompletionSource<" + response_type + ">> tuple;");
        WriteLineIndent("_requestsById" + response_name + ".TryGetValue(request.Value, out tuple);");
        WriteLineIndent("var timestamp = tuple.Item1;");
        WriteLineIndent("var timespan = tuple.Item2;");
        WriteLineIndent("if ((timestamp + timespan) <= utc)");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("var source = tuple.Item3;");
        WriteLineIndent("source.SetException(new Exception(\"Timeout!\"));");
        WriteLineIndent("_requestsById" + response_name + ".Remove(request.Value);");
        WriteLineIndent("_requestsByTimestamp" + response_name + ".Remove(timestamp);");
        WriteLineIndent("continue;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLineIndent("else");
        Indent(1);
        WriteLineIndent("break;");
        Indent(-1);
        Indent(-1);
        WriteLineIndent("}");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver message handler
    WriteLine();
    WriteLineIndent("internal override bool OnReceive(long type, byte[] buffer, long offset, long size) { return OnReceiveListener(this, type, buffer, offset, size); }");
    WriteLineIndent("internal bool OnReceiveListener(" + listener + " listener, long type, byte[] buffer, long offset, long size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case " + *s->name + model + ".FBETypeConst:");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent(*s->name + "ReceiverModel.Attach(buffer, offset);");
                WriteLineIndent("Debug.Assert(" + *s->name + "ReceiverModel.Verify(), \"" + domain + *p->name + "." + *s->name + " validation failed!\");");
                WriteLineIndent("long deserialized = " + *s->name + "ReceiverModel.Deserialize(out " + *s->name + "ReceiverValue);");
                WriteLineIndent("Debug.Assert((deserialized > 0), \"" + domain + *p->name + "." + *s->name + " deserialization failed!\");");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (Logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("string message = " + *s->name + "ReceiverValue.ToString();");
                WriteLineIndent("listener.OnReceiveLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("listener.OnReceive(" + *s->name + "ReceiverValue);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (p->import)
    {
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "Client.OnReceiveListener(listener, type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");

    WriteLine();

    // Generate response events
    for (const auto& response : responses)
    {
        std::string response_name = response;
        std::string response_type = ConvertTypeName(domain, *p->name, response, false);
        CppCommon::StringUtils::ReplaceAll(response_name, ".", "");

        WriteLineIndent("public delegate void ReceiveResponseHandler_" + response_name + "(" + response_type + " response);");
        WriteLineIndent("public event ReceiveResponseHandler_" + response_name + " ReceivedResponse_" + response_name + " = (response) => {};");
    }
    // Generate remaining response events
    if (p->body)
    {
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_response_name = *s->name;
                std::string struct_response_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_response_name, ".", "");

                if ((responses.find(*s->name) == responses.end()) && (cache.find(struct_response_type) == cache.end()))
                {
                    WriteLineIndent("public delegate void ReceiveResponseHandler_" + struct_response_name + "(" + struct_response_type + " response);");
                    WriteLineIndent("public event ReceiveResponseHandler_" + struct_response_name + " ReceivedResponse_" + struct_response_name + " = (response) => {};");
                    cache.insert(struct_response_type);
                }
            }
        }
    }

    // Generate reject events
    for (const auto& reject : rejects)
    {
        std::string reject_name = reject.first;
        std::string reject_type = ConvertTypeName(domain, *p->name, reject.first, false);
        CppCommon::StringUtils::ReplaceAll(reject_name, ".", "");

        WriteLineIndent("public delegate void ReceiveRejectHandler_" + reject_name + "(" + reject_type + " reject);");
        WriteLineIndent("public event ReceiveRejectHandler_" + reject_name + " ReceivedReject_" + reject_name + " = (reject) => {};");
    }
    // Generate remaining reject events
    if (p->body)
    {
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_reject_name = *s->name;
                std::string struct_reject_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_reject_name, ".", "");

                if ((rejects.find(*s->name) == rejects.end()) && (cache.find(struct_reject_type) == cache.end()))
                {
                    WriteLineIndent("public delegate void ReceiveRejectHandler_" + struct_reject_name + "(" + struct_reject_type + " reject);");
                    WriteLineIndent("public event ReceiveRejectHandler_" + struct_reject_name + " ReceivedReject_" + struct_reject_name + " = (reject) => {};");
                    cache.insert(struct_reject_type);
                }
            }
        }
    }

    // Generate notify events
    if (p->body)
    {
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_notify_name = *s->name;
                std::string struct_notify_type = ConvertTypeName(domain, *p->name, *s->name, false);
                CppCommon::StringUtils::ReplaceAll(struct_notify_name, ".", "");

                if (cache.find(struct_notify_type) == cache.end())
                {
                    WriteLineIndent("public delegate void ReceiveNotifyHandler_" + struct_notify_name + "(" + struct_notify_type + " notify);");
                    WriteLineIndent("public event ReceiveNotifyHandler_" + struct_notify_name + " ReceivedNotify_" + struct_notify_name + " = (notify) => {};");
                    cache.insert(struct_notify_type);
                }
            }
        }
    }

    // Generate client end
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    Indent(-1);
    WriteLine();
    WriteLineIndent("} // namespace " + domain + *p->name + ".FBE");
}

bool GeneratorCSharp::IsKnownType(const std::string& type)
{
    return ((type == "bool") ||
            (type == "byte") || (type == "bytes") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double") ||
            (type == "decimal") || (type == "string") ||
            (type == "timestamp") || (type == "uuid"));
}

bool GeneratorCSharp::IsReferenceType(const std::string& type)
{
    return ((type == "bytes") || (type == "string"));
}

std::string GeneratorCSharp::ConvertEnumRead(const std::string& type)
{
    if (type == "byte")
        return "ReadByte";
    else if (type == "char")
        return "ReadUInt8";
    else if (type == "wchar")
        return "ReadUInt32";
    else if (type == "int8")
        return "ReadInt8";
    else if (type == "uint8")
        return "ReadUInt8";
    else if (type == "int16")
        return "ReadInt16";
    else if (type == "uint16")
        return "ReadUInt16";
    else if (type == "int32")
        return "ReadInt32";
    else if (type == "uint32")
        return "ReadUInt32";
    else if (type == "int64")
        return "ReadInt64";
    else if (type == "uint64")
        return "ReadUInt64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorCSharp::ConvertEnumSize(const std::string& type)
{
    if (type == "byte")
        return "1";
    else if (type == "char")
        return "1";
    else if (type == "wchar")
        return "4";
    else if (type == "int8")
        return "1";
    else if (type == "uint8")
        return "1";
    else if (type == "int16")
        return "2";
    else if (type == "uint16")
        return "2";
    else if (type == "int32")
        return "4";
    else if (type == "uint32")
        return "4";
    else if (type == "int64")
        return "8";
    else if (type == "uint64")
        return "8";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorCSharp::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "byte";
    else if (type == "char")
        return "byte";
    else if (type == "wchar")
        return "ushort";
    else if (type == "int8")
        return "sbyte";
    else if (type == "uint8")
        return "byte";
    else if (type == "int16")
        return "short";
    else if (type == "uint16")
        return "ushort";
    else if (type == "int32")
        return "int";
    else if (type == "uint32")
        return "uint";
    else if (type == "int64")
        return "long";
    else if (type == "uint64")
        return "ulong";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorCSharp::ConvertEnumTypeUtf8Json(const std::string& type)
{
    if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "Byte";
    else if (type == "wchar")
        return "UInt16";
    else if (type == "int8")
        return "SByte";
    else if (type == "uint8")
        return "Byte";
    else if (type == "int16")
        return "Int16";
    else if (type == "uint16")
        return "UInt16";
    else if (type == "int32")
        return "Int32";
    else if (type == "uint32")
        return "UInt32";
    else if (type == "int64")
        return "Int64";
    else if (type == "uint64")
        return "UInt64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorCSharp::ConvertEnumConstant(const std::string& type, const std::string& value)
{
    std::string result = value;

    // Fill flags values
    std::vector<std::string> flags = CppCommon::StringUtils::Split(result, '|', true);

    // Generate flags combination
    if (flags.size() > 1)
    {
        result = "";
        bool first = true;
        for (const auto& it : flags)
        {
            std::string flag = CppCommon::StringUtils::ToTrim(it);
            result += (first ? "" : "_ | _") + flag;
            first = false;
        }
    }

    return ConvertEnumConstantPrefix(type) + result + ConvertEnumConstantSuffix(type);
}

std::string GeneratorCSharp::ConvertEnumConstantPrefix(const std::string& type)
{
    if (type == "byte")
        return "(byte)";
    else if (type == "char")
        return "(byte)";
    else if (type == "wchar")
        return "(ushort)";
    else if (type == "int8")
        return "(sbyte)";
    else if (type == "uint8")
        return "(byte)";
    else if (type == "int16")
        return "(short)";
    else if (type == "uint16")
        return "(ushort)";
    else if (type == "int32")
        return "(int)";
    else if (type == "uint32")
        return "(uint)";
    else if (type == "int64")
        return "(long)";
    else if (type == "uint64")
        return "(ulong)";

    return "";
}

std::string GeneratorCSharp::ConvertEnumConstantSuffix(const std::string& type)
{
    if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32"))
        return "U";
    else if (type == "int64")
        return "L";
    else if (type == "uint64")
        return "UL";

    return "";
}

std::string GeneratorCSharp::ConvertBaseTypeName(const std::string& type)
{
    return CppCommon::StringUtils::ToUpper(type);
}

std::string GeneratorCSharp::ConvertTypeName(const std::string& type, bool optional)
{
    if (optional)
    {
        if (type == "bytes")
            return "MemoryStream";
        if (type == "string")
            return "string";

        return ConvertTypeName(type, false) + "?";
    }

    if (type == "bool")
        return "bool";
    else if (type == "byte")
        return "byte";
    else if (type == "bytes")
        return "MemoryStream";
    else if (type == "char")
        return "char";
    else if (type == "wchar")
        return "char";
    else if (type == "int8")
        return "sbyte";
    else if (type == "uint8")
        return "byte";
    else if (type == "int16")
        return "short";
    else if (type == "uint16")
        return "ushort";
    else if (type == "int32")
        return "int";
    else if (type == "uint32")
        return "uint";
    else if (type == "int64")
        return "long";
    else if (type == "uint64")
        return "ulong";
    else if (type == "float")
        return "float";
    else if (type == "double")
        return "double";
    else if (type == "decimal")
        return "decimal";
    else if (type == "string")
        return "string";
    else if (type == "timestamp")
        return "DateTime";
    else if (type == "uuid")
        return "Guid";

    return type;
}

std::string GeneratorCSharp::ConvertTypeName(const std::string& domain, const std::string& package, const std::string& type, bool optional)
{
    return (CppCommon::StringUtils::Contains(type, '.') ? "" : ("global::" + domain + package + ".")) + ConvertTypeName(type, optional);
}

std::string GeneratorCSharp::ConvertTypeName(const StructField& field)
{
    if (field.array)
        return ConvertTypeName(*field.type, field.optional) + "[]";
    else if (field.vector)
        return "List<" + ConvertTypeName(*field.type, field.optional) + ">";
    else if (field.list)
        return "LinkedList<" + ConvertTypeName(*field.type, field.optional) + ">";
    else if (field.set)
        return "HashSet<" + ConvertTypeName(*field.key, false) + ">";
    else if (field.map)
        return "SortedDictionary<" + ConvertTypeName(*field.key, false) + ", " + ConvertTypeName(*field.type, field.optional) +">";
    else if (field.hash)
        return "Dictionary<" + ConvertTypeName(*field.key, false) + ", " + ConvertTypeName(*field.type, field.optional) +">";

    return ConvertTypeName(*field.type, field.optional);
}

std::string GeneratorCSharp::ConvertTypeFieldName(const std::string& domain, const std::string& package, const std::string& type, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    std::string ns = domain + package + ".FBE.";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns + "FBE.";
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + modelType + "Model" + ConvertTypeName(t, false);
}

std::string GeneratorCSharp::ConvertTypeFieldDeclaration(const std::string& domain, const std::string& package, const StructField& field, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";

        if (field.optional)
            name = "Optional" + name;

        StructField value;
        value.type = field.type;

        return domain + "FBE." + modelType + "ModelArray" + name + "<" + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">";
    }
    else if (field.vector || field.list || field.set)
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";

        if (field.optional)
            name = "Optional" + name;

        StructField value;
        value.type = field.type;

        return domain + "FBE." + modelType + "ModelVector" + name + "<" + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">";
    }
    else if (field.map || field.hash)
    {
        std::string key_name = IsReferenceType(*field.key) ? "ReferenceTypeKey" : "ValueTypeKey";
        std::string value_name = IsReferenceType(*field.type) ? "ReferenceTypeValue" : "ValueTypeValue";

        if (field.optional)
            value_name = "Optional" + value_name;

        StructField key;
        key.type = field.key;

        StructField value;
        value.type = field.type;

        return domain + "FBE." + modelType + "ModelMap" + key_name + value_name + "<" + ConvertTypeName(*field.key, false) + ", " + ConvertTypeFieldDeclaration(domain, package, key, final) + ", " + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">";
    }
    else if (field.optional)
    {
        StructField value;
        value.type = field.type;

        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";
        return domain + "FBE." + modelType + "ModelOptional" + name + "<" + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">";
    }
    else if (IsKnownType(*field.type))
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";
        return domain + "FBE." + modelType + "Model" + name + "<" + ConvertTypeName(*field.type, field.optional) + ">";
    }

    return ConvertTypeFieldName(domain, package, *field.type, final);
}

std::string GeneratorCSharp::ConvertTypeFieldInitialization(const std::string& domain, const std::string& package, const StructField& field, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";

        if (field.optional)
            name = "Optional" + name;

        StructField value;
        value.type = field.type;

        return "new " + domain + "FBE." + modelType + "ModelArray" + name + "<" + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">(" + ConvertTypeFieldInitialization(domain, package, value, offset, final) + ", buffer, " + offset + ", " + std::to_string(field.N) + ")";
    }
    else if (field.vector || field.list || field.set)
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";

        if (field.optional)
            name = "Optional" + name;

        StructField value;
        value.type = field.type;

        return "new " + domain + "FBE." + modelType + "ModelVector" + name + "<" + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">(" + ConvertTypeFieldInitialization(domain, package, value, offset, final) + ", buffer, " + offset + ")";
    }
    else if (field.map || field.hash)
    {
        std::string key_name = IsReferenceType(*field.key) ? "ReferenceTypeKey" : "ValueTypeKey";
        std::string value_name = IsReferenceType(*field.type) ? "ReferenceTypeValue" : "ValueTypeValue";

        if (field.optional)
            value_name = "Optional" + value_name;

        StructField key;
        key.type = field.key;

        StructField value;
        value.type = field.type;

        return "new " + domain + "FBE." + modelType + "ModelMap" + key_name + value_name + "<" + ConvertTypeName(*field.key, false) + ", " + ConvertTypeFieldDeclaration(domain, package, key, final) + ", " + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, value, final) + ">(" + ConvertTypeFieldInitialization(domain, package, key, offset, final) + ", " + ConvertTypeFieldInitialization(domain, package, value, offset, final) + ", buffer, " + offset + ")";
    }
    else if (field.optional)
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";

        StructField model;
        model.type = field.type;

        StructField value;
        value.type = field.type;

        return "new " + domain + "FBE." + modelType + "ModelOptional" + name + "<" + ConvertTypeName(*field.type, false) + ", " + ConvertTypeFieldDeclaration(domain, package, model, final) + ">(" + ConvertTypeFieldInitialization(domain, package, value, offset, final) + ", buffer, " + offset + ")";
    }
    else if (IsKnownType(*field.type))
    {
        std::string name = IsReferenceType(*field.type) ? "ReferenceType" : "ValueType";
        return domain + "FBE." + modelType + "Model" + name + "<" + ConvertTypeName(*field.type, field.optional) + ">.Create" + modelType + "Model(" + domain + "FBE.BaseTypes." + ConvertBaseTypeName(*field.type) + ", buffer, " + offset + ")";
    }

    return "new " + ConvertTypeFieldName(domain, package, *field.type, final) + "(buffer, " + offset + ")";
}

std::string GeneratorCSharp::ConvertConstant(const std::string& domain, const std::string& package, const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "true";
    else if (value == "false")
        return "false";
    else if (value == "null")
        return "null";
    else if (value == "min")
    {
        if (type == "byte")
            return "Byte.MinValue";
        else if (type == "int8")
            return "SByte.MinValue";
        else if (type == "uint8")
            return "Byte.MinValue";
        else if (type == "int16")
            return "Int16.MinValue";
        else if (type == "uint16")
            return "UInt16.MinValue";
        else if (type == "int32")
            return "Int32.MinValue";
        else if (type == "uint32")
            return "UInt32.MinValue";
        else if (type == "int64")
            return "Int64.MinValue";
        else if (type == "uint64")
            return "UInt64.MinValue";

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return "Byte.MaxValue";
        else if (type == "int8")
            return "SByte.MaxValue";
        else if (type == "uint8")
            return "Byte.MaxValue";
        else if (type == "int16")
            return "Int16.MaxValue";
        else if (type == "uint16")
            return "UInt16.MaxValue";
        else if (type == "int32")
            return "Int32.MaxValue";
        else if (type == "uint32")
            return "UInt32.MaxValue";
        else if (type == "int64")
            return "Int64.MaxValue";
        else if (type == "uint64")
            return "UInt64.MaxValue";

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return "new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)";
    else if (value == "utc")
        return "DateTime.UtcNow";
    else if (value == "uuid0")
        return domain + "FBE.UuidGenerator.Nil()";
    else if (value == "uuid1")
        return domain + "FBE.UuidGenerator.Sequential()";
    else if (value == "uuid2")
        return domain + "FBE.UuidGenerator.Random()";

    std::string result = value;

    if (!IsKnownType(type))
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (flags.size() > 1)
        {
            result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                std::string flag = CppCommon::StringUtils::ToTrim(it);
                std::string ns = (CppCommon::StringUtils::CountAll(flag, ".") > 1) ? ("global::" + domain) : ("global::" + domain + package + ".");
                result += (first ? "" : " | ") + ns + flag;
                first = false;
            }
        }
        else
        {
            std::string ns = (CppCommon::StringUtils::CountAll(result, ".") > 1) ? ("global::" + domain) : ("global::" + domain + package + ".");
            result = ns + result;
        }
    }

    return ConvertConstantPrefix(type) + result + ConvertConstantSuffix(type);
}

std::string GeneratorCSharp::ConvertConstantPrefix(const std::string& type)
{
    if (type == "bool")
        return "(bool)";
    else if (type == "byte")
        return "(byte)";
    else if (type == "char")
        return "(char)";
    else if (type == "wchar")
        return "(char)";
    else if (type == "int8")
        return "(sbyte)";
    else if (type == "uint8")
        return "(byte)";
    else if (type == "int16")
        return "(short)";
    else if (type == "uint16")
        return "(ushort)";
    else if (type == "int32")
        return "(int)";
    else if (type == "uint32")
        return "(uint)";
    else if (type == "int64")
        return "(long)";
    else if (type == "uint64")
        return "(ulong)";
    else if (type == "float")
        return "(float)";
    else if (type == "double")
        return "(double)";
    else if (type == "decimal")
        return "(decimal)";
    else if (type == "uuid")
        return "Guid.Parse(";

    return "";
}

std::string GeneratorCSharp::ConvertConstantSuffix(const std::string& type)
{
    if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32"))
        return "U";
    else if (type == "int64")
        return "L";
    else if (type == "uint64")
        return "UL";
    else if (type == "float")
        return "F";
    else if (type == "double")
        return "D";
    else if (type == "decimal")
        return "M";
    else if (type == "uuid")
        return ")";

    return "";
}

std::string GeneratorCSharp::ConvertDefault(const std::string& domain, const std::string& package, const std::string& type)
{
    if (type == "bool")
        return "false";
    else if (type == "bytes")
        return "new MemoryStream()";
    else if ((type == "char") || (type == "wchar"))
        return "'\\0'";
    else if ((type == "byte") || (type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64"))
        return ConvertConstantPrefix(type) + "0" + ConvertConstantSuffix(type);
    else if (type == "float")
        return "0.0F";
    else if (type == "double")
        return "0.0D";
    else if (type == "decimal")
        return "0.0M";
    else if (type == "string")
        return "\"\"";
    else if (type == "timestamp")
        return "new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)";
    else if (type == "uuid")
        return domain + "FBE.UuidGenerator.Nil()";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else if (!package.empty())
        ns = domain + package + ".";

    return "new " + ns + t + "()";
}

std::string GeneratorCSharp::ConvertDefault(const std::string& domain, const std::string& package, const StructField& field)
{
    if (field.value)
        return ConvertConstant(domain, package, *field.type, *field.value, field.optional);

    if (field.array)
        return "new " + ConvertTypeName(*field.type, field.optional) + "[" + std::to_string(field.N) + "]";
    else if (field.vector || field.list || field.set || field.map || field.hash)
        return "new " + ConvertTypeName(field) + "()";
    else if (field.optional)
        return "null";
    else if (!IsKnownType(*field.type))
    {
        std::string ns = "global::" + domain + (CppCommon::StringUtils::Contains(*field.type, '.') ? "" : (package + "."));
        return ns + ConvertTypeName(field) + ".Default";
    }

    return ConvertDefault(domain, package, *field.type);
}

std::string GeneratorCSharp::ConvertOutputStreamType(const std::string& type, const std::string& name, bool optional)
{
    if (type == "bool")
        return ".Append(" + name + (optional ? ".Value" : "") + " ? \"true\" : \"false\")";
    else if (type == "bytes")
        return ".Append(\"bytes[\").Append(" + name + ".Length).Append(\"]\")";
    else if ((type == "char") || (type == "wchar"))
        return ".Append(\"'\").Append(" + name + ").Append(\"'\")";
    else if ((type == "float") || (type == "double") || (type == "decimal"))
        return ".Append(" + name + (optional ? ".Value" : "") + ".ToString(CultureInfo.InvariantCulture))";
    else if ((type == "string") || (type == "uuid"))
        return ".Append(\"\\\"\").Append(" + name + ").Append(\"\\\"\")";
    else if (type == "timestamp")
        return ".Append((ulong)((" + name + (optional ? ".Value" : "") + ".Ticks - 621355968000000000) * 100))";
    else
        return ".Append(" + name + ")";
}

std::string GeneratorCSharp::ConvertOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
{
    std::string comma = separate ? ".Append(first ? \"\" : \",\")" : "";

    if (optional || (type == "bytes") || (type == "string"))
        return "if (" + name + " != null) sb" + comma + ConvertOutputStreamType(type, name, true) + "; else sb" + comma + ".Append(\"null\");";
    else
        return "sb" + comma + ConvertOutputStreamType(type, name, false) + ";";
}

} // namespace FBE
